Phalcon PHP Framework Documentation Release 3.1.1

Phalcon Team

Jun 23, 2017

Contents

1 Apa itu Phalcon 3

2 Daftar Isi 5 2.1 Installation...... 5 2.1.1 Installation...... 5 2.1.2 Phalcon Developer Tools...... 25 2.2 Tutorials...... 45 2.2.1 Tutorial 1: Let’s learn by example...... 45 2.2.2 Tutorial 2: Introducing INVO...... 56 2.2.3 Tutorial 3: Securing INVO...... 61 2.2.4 Tutorial 4: Working with the CRUD...... 70 2.2.5 Tutorial 5: Customizing INVO...... 86 2.2.6 Tutorial 6: Vökuró...... 88 2.2.7 Tutorial 7: Creating a Simple REST API...... 92 2.2.8 List of examples...... 102 2.3 Components...... 103 2.3.1 Injeksi Ketergantungan/Lokasi Service...... 103 2.3.2 The MVC Architecture...... 116 2.3.3 Menggunakan Kontroler...... 117 2.3.4 Working with Models...... 124 2.3.5 Model Relationships...... 142 2.3.6 Model Events...... 156 2.3.7 Model Behaviors...... 162 2.3.8 Models Metadata...... 167 2.3.9 Model Transactions...... 172 2.3.10 Validating Models...... 176 2.3.11 Working with Models (Advanced)...... 179 2.3.12 Phalcon Query Language (PHQL)...... 189 2.3.13 Caching in the ORM...... 206 2.3.14 ODM (Object-Document Mapper)...... 219 2.3.15 Using Views...... 232 2.3.16 View Helpers (Tags)...... 252 2.3.17 Pengelolaan Aset...... 264 2.3.18 Volt: Template Engine...... 271 2.3.19 Aplikasi MVC...... 295 2.3.20 Routing...... 302

i 2.3.21 Dispatch Kontroler...... 323 2.3.22 Micro Applications...... 334 2.3.23 Working with Namespaces...... 349 2.3.24 Events Manager...... 351 2.3.25 Request Environment...... 358 2.3.26 Returning Responses...... 361 2.3.27 Pengelolaan Cookies...... 364 2.3.28 Generating URLs and Paths...... 366 2.3.29 Pesan Flash...... 369 2.3.30 Storing data in Session...... 373 2.3.31 Penyaringan dan Sanitasi...... 376 2.3.32 Escape Kontekstual...... 380 2.3.33 Validation...... 384 2.3.34 Forms...... 391 2.3.35 Membaca Konfigurasi...... 402 2.3.36 Pagination...... 406 2.3.37 Meningkatkan Performa dengan Cache...... 409 2.3.38 Security...... 417 2.3.39 Enkripsi/Dekripsi...... 421 2.3.40 Access Control Lists (ACL)...... 423 2.3.41 Multi-lingual Support...... 432 2.3.42 Class Autoloader...... 435 2.3.43 Logging...... 440 2.3.44 Parser Anotasi...... 446 2.3.45 Aplikasi Command Line...... 452 2.3.46 Images...... 456 2.3.47 Queueing...... 461 2.3.48 Lapisan Abstraksi Database...... 463 2.3.49 Internationalization...... 478 2.3.50 Database Migrations...... 480 2.3.51 Mendebug Aplikasi...... 488 2.3.52 Unit testing...... 492 2.4 In Depth Explanations / Further Reading...... 496 2.4.1 Increasing Performance: What’s next?...... 496 2.4.2 Dependency Injection Explained...... 504 2.4.3 Understanding How Phalcon Applications Work...... 510 2.5 API...... 514 2.5.1 API Indice...... 514 2.6 Legal...... 1110 2.6.1 License...... 1110

3 Previous Versions 1111

4 Format lain 1113

ii Phalcon PHP Framework Documentation, Release 3.1.1

Selamat datang di Phalcon framework. Misi kami adalah memberimu tool canggih untuk mengembangkan website dan aplikasi web yang lebih cepat dengan PHP.

Contents 1 Phalcon PHP Framework Documentation, Release 3.1.1

2 Contents CHAPTER 1

Apa itu Phalcon

Phalcon adalah framework PHP full stack open source, ditulis sebagai ekstensi , dioptimasi untuk performa tinggi. Anda tidak perlu belajar atau menggunakan bahasa C, karena fungsionalitasnya disediakan sebagai kelas PHP siap untuk Anda gunakan. Phalcon juga loose-coupled, memungkinkan Anda menggunakan objek-objeknya sebagai kom- ponen perekat sesuai kebutuhan aplikasi Anda. Phalcon tidak hanya tentang performa, tujuan kami adalah membuatnya kokoh, kaya fitur dan mudah digunakan!

3 Phalcon PHP Framework Documentation, Release 3.1.1

4 Chapter 1. Apa itu Phalcon CHAPTER 2

Daftar Isi

Installation

Installation

PHP extensions require a slightly different installation method to a traditional PHP-based or framework. You can either download a binary package for the system of your choice or build it from the sources.

Windows

To use phalcon on Windows you can download a DLL library. Edit your .ini file and then append at the end: extension=php_phalcon.dll

Restart your webserver. The following screencast is a step-by-step guide to install Phalcon on Windows:

Related Guides

Installation on XAMPP

XAMPP is an easy to install Apache distribution containing MySQL, PHP and . Once you download XAMPP, all you have to do is extract it and start using it. Below are detailed instructions on how to install Phalcon on XAMPP for Windows. Using the latest XAMPP version is highly recommended.

Download the right version of Phalcon

XAMPP is always releasing 32 bit versions of Apache and PHP. You will need to download the x86 version of Phalcon for Windows from the download section.

5 Phalcon PHP Framework Documentation, Release 3.1.1

After downloading the Phalcon library you will have a zip file like the one shown below:

Extract the library from the archive to get the Phalcon DLL:

Copy the file php_phalcon.dll to the PHP extensions. If you have installed XAMPP in the C:\xampp folder, the extension needs to be in C:\xampp\php\ext Edit the php.ini file, it is located at C:\xampp\php\php.ini. It can be edited with Notepad or a similar program. We recommend Notepad++ to avoid issues with line endings. Append at the end of the file: extension=php_phalcon.dll and save it. Restart the Apache Web Server from the XAMPP Control Center. This will load the new PHP configuration. Open your browser to navigate to http://localhost. The XAMPP welcome page will appear. Click on the link phpinfo(). phpinfo() will output a significant amount of information on screen about the current state of PHP. Scroll down to check if the phalcon extension has been loaded correctly. If you can see the phalcon version in the phpinfo() output, congrats!, You are now flying with Phalcon.

Screencast

The following screencast is a step by step guide to install Phalcon on Windows:

Related Guides

• General Installation • Detailed Installation on WAMP for Windows

6 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

2.1. Installation 7 Phalcon PHP Framework Documentation, Release 3.1.1

8 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

2.1. Installation 9 Phalcon PHP Framework Documentation, Release 3.1.1

Installation on WAMP

WampServer is a Windows web development environment. It allows you to create web applications with Apache2, PHP and a MySQL database. Below are detailed instructions on how to install Phalcon on WampServer for Windows. Using the latest WampServer version is highly recommended.

Download the right version of Phalcon

WAMP has both 32 and 64 bit versions. From the download section, you can choose the Phalcon for Windows accordingly to your desired architecture. After download the Phalcon library you will have a zip file like the one shown below:

Extract the library from the archive to get the Phalcon DLL:

Copy the file php_phalcon.dll to the PHP extensions. If WAMP is installed in the C:\wamp folder, the extension needs to be in C:\wamp\bin\php\php5.5.12\ext Edit the php.ini file, it is located at C:\wamp\bin\php\php5.5.12\php.ini. It can be edited with Notepad or a simi- lar program. We recommend Notepad++ to avoid issues with line endings. Append at the end of the file: exten- sion=php_phalcon.dll and save it. Also edit another php.ini file, which is located at C:\wamp\bin\apache\apache2.4.9\bin\php.ini. Append at the end of the file: extension=php_phalcon.dll and save it. Restart the Apache Web Server. Do a single click on the WampServer icon at system tray. Choose “Restart All Services” from the pop-up menu. Check out that tray icon will become green again. Open your browser to navigate to http://localhost. The WAMP welcome page will appear. Look at the section “exten- sions loaded” to check if phalcon was loaded. Congrats!, You are now flying with Phalcon.

10 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

2.1. Installation 11 Phalcon PHP Framework Documentation, Release 3.1.1

12 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Related Guides

• General Installation • Detailed Installation on XAMPP for Windows

Linux/Solaris

Debian / Ubuntu

To add the repository to your distribution:

# Stable releases curl -s https://packagecloud.io/install/repositories/phalcon/stable/script.deb.sh |

˓→sudo bash

# Nightly releases curl -s https://packagecloud.io/install/repositories/phalcon/nightly/script.deb.sh |

˓→sudo bash

This only needs to be done only once, unless your distribution changes or you want to switch from stable to nightly builds. To install Phalcon:

2.1. Installation 13 Phalcon PHP Framework Documentation, Release 3.1.1

sudo -get install php5-phalcon

# or for PHP 7 sudo apt-get install php7.0-phalcon

RPM distributions (i.e. CentOS)

To add the repository to our distribution:

# Stable releases curl -s https://packagecloud.io/install/repositories/phalcon/stable/script.rpm.sh |

˓→sudo bash

# Nightly releases curl -s https://packagecloud.io/install/repositories/phalcon/nightly/script.rpm.sh |

˓→sudo bash

This only needs to be done only once, unless your distribution changes or you want to switch from stable to nightly builds. To install Phalcon: sudo install php56u-phalcon

# or for PHP 7 sudo yum install php70u-phalcon

Compile from source

On a /Solaris system you can easily compile and install the extension from the : Prerequisite packages are: • PHP >= 5.5 development resources • GCC compiler (Linux/Solaris) • Git (if not already installed in your system - unless you download the package from GitHub and upload it on your server via FTP/SFTP) Specific packages for common platforms:

# Ubuntu sudo apt-get install php5-dev libpcre3-dev gcc make php5-mysql

# Suse sudo yast -i gcc make autoconf php5-devel php5- php5-mysql

# CentOS/RedHat/Fedora sudo yum install php-devel pcre-devel gcc make

# Solaris pkg install gcc-45 php-56 apache-php56

14 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Creating the extension: git clone git://github.com/phalcon/cphalcon.git cd cphalcon/build sudo ./install

Add extension to your PHP configuration:

# Suse: Add a file called phalcon.ini in /etc/php5/conf./ with this content: extension=phalcon.so

# CentOS/RedHat/Fedora: Add a file called phalcon.ini in /etc/php.d/ with this

˓→content: extension=phalcon.so

# Ubuntu/Debian with apache2: Add a file called 30-phalcon.ini in /etc/php5/apache2/

˓→conf.d/ with this content: extension=phalcon.so

# Ubuntu/Debian with php5-fpm: Add a file called 30-phalcon.ini in /etc/php5/fpm/conf.

˓→d/ with this content: extension=phalcon.so

# Ubuntu/Debian with php5-cli: Add a file called 30-phalcon.ini in /etc/php5/cli/conf.

˓→d/ with this content: extension=phalcon.so

Restart the webserver. If you are running Ubuntu/Debian with php5-fpm, restart it: sudo service php5-fpm restart

Phalcon automatically detects your architecture, however, you can force the compilation for a specific architecture: cd cphalcon/build

# One of the following: sudo ./install 32bits sudo ./install 64bits sudo ./install safe

If the automatic installer fails try building the extension manually: cd cphalcon/build/64bits export CFLAGS="-O2 --fvisibility=hidden"

./configure --enable-phalcon make&& sudo make install

Mac OS X

On a Mac OS X system you can compile and install the extension from the source code:

2.1. Installation 15 Phalcon PHP Framework Documentation, Release 3.1.1

Requirements

Prerequisite packages are: • PHP >= 5.5 development resources • XCode

# brew brew tap homebrew/homebrew-php brew install php55-phalcon brew install php56-phalcon

# MacPorts sudo port install php55-phalcon sudo port install php56-phalcon

Add extension to your PHP configuration.

FreeBSD

A port is available for FreeBSD. Just only need these simple line commands to install it: pkg_add -r phalcon or export CFLAGS="-O2 --fvisibility=hidden" cd /usr/ports/www/phalcon make install clean

Checking your installation

Check your phpinfo() output for a section referencing “Phalcon” or execute the code snippet below:

The Phalcon extension should appear as part of the output:

Array ( [0] => Core [1] => libxml [2] => filter [3] => SPL [4] => standard [5] => phalcon [6] => pdo_mysql )

Installation Notes

Installation notes for Web Servers:

16 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Catatan Instalasi Apache

Apache adalah web server popular dan terkenal yang tersedia di beragam platform.

Konfigurasi Apache untuk Phalcon

Berikut ini adalah konfigurasi yang bisa anda gunakan untuk setup Apache dengan Phalcon. Catatan ini utamanya fokus pada konfigurasi modul mod_rewrite yang memungkinkan penggunaan URL yang ramah dan router component. Aplikasi biasanya menggunakan struktur berikut:

test/ app/ controllers/ models/ views/ public/ css/ img/ js/ index.php

Directory dalam Document Root utama

Ini adalah kasus paling umum, aplikasi diinstall pada sembarang direktori dalam document root. Pada kasus ini, kita menggunakan dua file .htaccess, yang pertama untuk menyembunyikan kode aplikasi dengan mengarahkan semua request ke document root aplikasi (public/).

# test/.htaccess

RewriteEngine on RewriteRule ^$ public/ [L] RewriteRule ((?s).*) public/$1 [L]

File .htaccess kedua diletakkan di direktori public/ , file ini menulis ulang semua URI ke file public/index.php:

# test/public/.htaccess

RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^((?s).*)$ index.php?_url=/$1 [QSA,L]

Jika anda tidak ingin menggunakan file .htaccess anda dapat memindahkan konfigurasi ini ke file konfigurasi utama Apache:

RewriteEngine on RewriteRule ^$ public/ [L]

2.1. Installation 17 Phalcon PHP Framework Documentation, Release 3.1.1

RewriteRule ((?s).*) public/$1 [L]

RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^((?s).*)$ index.php?_url=/$1 [QSA,L]

Virtual Host

Konfigurasi kedua memungkinkan anda menginstall aplikasi Phalcon dalam sebuah virtual host:

ServerAdmin [email protected] DocumentRoot "/var/vhosts/test/public" DirectoryIndex index.php ServerName example.host ServerAlias www.example.host

Options All AllowOverride All Allow from all

Atau jika Anda menggunakan Apache 2.4 atau diatasnya:

ServerAdmin [email protected] DocumentRoot "/var/vhosts/test/public" DirectoryIndex index.php ServerName example.host ServerAlias www.example.host

Options All AllowOverride All Require all granted

Nginx Installation Notes

Nginx is a free, open-source, high-performance HTTP server and reverse proxy, as well as an IMAP/POP3 proxy server. Unlike traditional servers, Nginx doesn’t rely on threads to handle requests. Instead it uses a much more

18 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1 scalable event-driven (asynchronous) architecture. This architecture uses small, but more importantly, predictable amounts of memory under load. The PHP-FPM (FastCGI Process Manager) is usually used to allow Nginx to process PHP files. Nowadays, PHP-FPM is bundled with any PHP distribution. Phalcon + Nginx+ PHP-FPM provides a powerful set of tools that offer maximum performance for your PHP applications.

Configuring Nginx for Phalcon

The following are potential configurations you can use to setup nginx with Phalcon:

Basic configuration

Using $_GET['_url'] as source of URIs: server { listen 80; server_name localhost.dev; root /var/www/phalcon/public; index index.php index. index.htm; charset utf-8;

location /{ try_files $uri $uri/ /index.php?_url=$uri&$args; }

location ~ \.php{ fastcgi_pass unix:/run/php-fpm/php-fpm.sock; fastcgi_index /index.php;

include fastcgi_params; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; }

location ~ /\.ht{ deny all; } }

Using $_SERVER['REQUEST_URI'] as source of URIs: server { listen 80; server_name localhost.dev; root /var/www/phalcon/public; index index.php index.html index.htm; charset utf-8;

location /{ try_files $uri $uri/ /index.php; }

location ~ \.php${

2.1. Installation 19 Phalcon PHP Framework Documentation, Release 3.1.1

try_files $uri=404;

fastcgi_pass 127.0.0.1:9000; fastcgi_index /index.php;

include fastcgi_params; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; }

location ~ /\.ht{ deny all; } }

Catatan Instalasi Cherokee

Cherokee adalah web server berkinerja tinggi. Ia sangat cepat, fleksibel dan mudah dikonfigurasi.

Mengkonfigurasi Cherokee untuk Phalcon

Cherokee menyediakan antarmuka grafis bersahabat untuk mengkonfigurasi hampir semua pengaturan yang tersedia di web server ini. Mulai sebagai administrator cherokee dengan menjalankan sebagai root /path-to- cherokee/sbin/cherokee-admin Menitpakan virtual host baru dengan mengklik pada ‘vServers’, lalu menambahkan virtual server baru: Virtual server yang baru ditambahkan muncul di sisi kiri layar. Di tab ‘Behaviors’ anda akan melihat default behaviors untuk virtual server ini. Klik tombol ‘Rule Management’. Hapus yang ditandai sebagai ‘Directory /cherokee_themes’ dan ‘Directory /icons’: Tambahkan ‘PHP Language’ behavior menggunakan wizard. Behavior ini memungkinkan anda menjalankan aplikasi PHP: Normalnya behavior ini tidak membutuhkan pengaturan tambahan. Tambahkan behavior lain, kali ini dibagian ‘Man- ual Configuration’. Di ‘Rule Type’ pilih ‘File Exists’, lalu pastikan pilihan ‘Match any file’ dihidupkan: Di tab ‘Handler’ pilih ‘List & Send’ sebagai handler: Edit behavior default ‘Default’ untuk menghidupkan URL-rewrite engine. Ubah handler ke ‘Redirection’, lalu tam- bahkan regular expression berikut ke engine ^(.*)$: Akhirnya, pastikan behavior memiliki urutan berikut: Eksekusi aplikasi di browser:

Menggunakan Web Server bawaan PHP

Mulai PHP 5.4.0, anda dapt menggunakan web server bawaan PHP untuk pengembangan. Untuk memulai server ketik: php -S localhost:8000 -t /public

20 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

2.1. Installation 21 Phalcon PHP Framework Documentation, Release 3.1.1

22 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

2.1. Installation 23 Phalcon PHP Framework Documentation, Release 3.1.1

24 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Jika anda ingin menulis ulang URI ke file index.php gunakan file router berikut (.htrouter.php):

Lau arahkan browser anda ke http://localhost:8000/ untuk menguji apakah semuanya bekerja.

Phalcon Developer Tools

These tools are a collection of useful scripts to generate skeleton code. Core components of your application can be generated with a simple command, allowing you to easily develop applications using Phalcon. If you prefer to use the web version instead of the console, this blog post offers more information.

Download

You can download or clone a cross platform package containing the developer tools from Github.

2.1. Installation 25 Phalcon PHP Framework Documentation, Release 3.1.1

Installation

These are detailed instructions on how to install the developer tools on different platforms:

Phalcon Developer Tools on Windows

These steps will guide you through the process of installing Phalcon Developer Tools for Windows.

Prerequisites

The Phalcon PHP extension is required to run Phalcon Tools. If you haven’t installed it yet, please see the Installation section for instructions.

Download

You can download a cross platform package containing the developer tools from the Download section. Also you can clone it from Github. On the Windows platform, you need to configure the system PATH to include Phalcon tools as well as the PHP executable. If you download the Phalcon tools as a zip archive, extract it on any path of your local drive i.e. c:\phalcon- tools. You will need this path in the steps below. Edit the file “phalcon.bat” by right clicking on the file and selecting “Edit”: Change the path to the one you installed the Phalcon tools (set PTOOLSPATH=C:phalcon-tools): Save the changes.

Adding PHP and Tools to your system PATH

Because the scripts are written in PHP, you need to install it on your machine. Depending on your PHP installation, the executable can be located in various places. Search for the file php.exe and copy the path it is located in. For instance, if using the latest WAMP stack, PHP is located in: C:\wampbin\php\php5.3.10\php.exe. From the Windows start menu, right mouse click on the “Computer” icon and select “Properties”: Click the “Advanced” tab and then the button “Environment Variables”: At the bottom, look for the section “System variables” and edit the variable “Path”: Be very careful on this step! You need to append at the end of the long string the path where your php.exe was located and the path where Phalcon tools are installed. Use the ”;” character to separate the different paths in the variable: Accept the changes made by clicking “OK” and close the dialogs opened. From the start menu click on the option “Run”. If you can’t find this option, press “Windows Key” + “R”. Type “cmd” and press enter to open the windows command line utility: Type the commands “php -v” and “phalcon” and you will see something like this: Congratulations you now have Phalcon tools installed!

26 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

2.1. Installation 27 Phalcon PHP Framework Documentation, Release 3.1.1

28 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

2.1. Installation 29 Phalcon PHP Framework Documentation, Release 3.1.1

30 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

2.1. Installation 31 Phalcon PHP Framework Documentation, Release 3.1.1

Related Guides

• Using Developer Tools • Installation on OS X • Installation on Linux

Phalcon Developer Tools on Mac OS X

These steps will guide you through the process of installing Phalcon Developer Tools for OS/X.

Prerequisites

The Phalcon PHP extension is required to run Phalcon Tools. If you haven’t installed it yet, please see the Installation section for instructions.

Download

You can download a cross platform package containing the developer tools from the Download section. You can also clone it from Github. Open the terminal application:

Copy & Paste the commands below in your terminal: git clone git://github.com/phalcon/phalcon-devtools.git

Then enter the folder where the tools were cloned and execute ”. ./phalcon.sh”, (don’t forget the dot at beginning of the command):

32 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

cd phalcon-devtools/

. ./phalcon.sh

In the terminal window, type the following commands to create a symbolic link to the phalcon.php script: ln -s ~/phalcon-tools/phalcon.php ~/phalcon-tools/phalcon chmod +x ~/phalcon-tools/phalcon

Type the command “phalcon” and you will see something like this:

Congratulations you now have Phalcon tools installed!

Related Guides

• Using Developer Tools • Installation on Windows • Installation on Linux

Phalcon Developer Tools di Linux

Langkah berikut akan memandu anda melalui proses menginstal Phalcon Developer Tools untuk Linux.

2.1. Installation 33 Phalcon PHP Framework Documentation, Release 3.1.1

Prasyarat

Ekstensi PHP Phalcon diperlukan untuk menjalankan Phalcon Tools. Jika belum menginstall, silakan lihat Installation untuk instruksi.

Download

Anda dapat mendownload cross platform package berisi developer tools dari bagian Download. Anda dapat juga clone dari Github. Buka terminal dan ketik perintah berikut: git clone git://github.com/phalcon/phalcon-devtools.git

Lalu masuk ke folder di mana tools diclone dan jalankan ”. ./phalcon.sh”, (Jangan lupa titik di awal perintah): cd phalcon-devtools/

. ./phalcon.sh

Buat symbolink ke script phalcon.php: ln -s ~/phalcon-devtools/phalcon.php /usr/bin/phalcon chmod ugo+x /usr/bin/phalcon

Selamat Phalcon tools anda sudah terinstall!

34 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Panduan Terkait

• Menggunakan Developer Tools • Instalasi pada Windows • Instalasi pada Mac

Getting Available Commands

You can get a list of available commands in Phalcon tools by typing: phalcon commands

$ phalcon commands

Phalcon DevTools(3.0.0)

Available commands: commands(alias of: list, enumerate) controller(alias of: create-controller) module(alias of: create-module) model(alias of: create-model) all-models(alias of: create-all-models) project(alias of: create-project) (alias of: create-scaffold) migration(alias of: create-migration) webtools(alias of: create-webtools)

2.1. Installation 35 Phalcon PHP Framework Documentation, Release 3.1.1

Generating a Project Skeleton

You can use Phalcon tools to generate pre-defined project skeletons for your applications with Phalcon framework. By default the project skeleton generator will use mod_rewrite for Apache. Type the following command on your web server document root:

$ pwd

/Applications/MAMP/htdocs

$ phalcon create-project store

The above recommended project structure was generated: You could add the parameter –help to get help on the usage of a certain script:

$ phalcon project --help

Phalcon DevTools(3.0.0)

Help: Creates a project

Usage: project[name][type][directory][enable-webtools]

Arguments: help Shows this help text

Example phalcon project store simple

Options: --name Name of the new project --enable-webtools Determines if webtools should be enabled[optional] --directory=s Base path on which project will be created[optional] --type=s Type of the application to be generated(cli, micro, simple,

˓→modules) --template-path=s Specify a template path[optional] --use-config-ini Use a ini file as configuration file[optional] --trace Shows the trace of the framework in case of exception.

˓→[optional] --help Shows this help

Accessing the project from the web server will show you:

Generating Controllers

The command “create-controller” generates controller skeleton structures. It’s important to invoke this command inside a directory that already has a Phalcon project.

$ phalcon create-controller --name test

The following code is generated by the script:

36 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

2.1. Installation 37 Phalcon PHP Framework Documentation, Release 3.1.1

use Phalcon\Mvc\Controller;

class TestController extends Controller { public function indexAction() {

} }

Preparing Database Settings

When a project is generated using developer tools. A configuration file can be found in app/config/config.ini To generate models or scaffold, you will need to change the settings used to connect to your database. Change the database section in your config.ini file:

[database] adapter= Mysql host= "127.0.0.1" username= "root" password= "secret" dbname= "store_db"

[phalcon] controllersDir= "../app/controllers/"

38 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

modelsDir= "../app/models/" viewsDir= "../app/views/" baseUri= "/store/"

Generating Models

There are several ways to create models. You can create all models from the default database connection or some selectively. Models can have public attributes for the field representations or setters/getters can be used. Options: --name=s Table name --schema=s Name of the schema. [optional] --namespace=s Model’s namespace [optional] --get-set Attributes will be protected and have setters/getters. [optional] --extends=s Model extends the class name supplied [optional] --excludefields=l Excludes fields defined in a comma separated list [optional] --doc Helps to improve code completion on IDEs [optional] --directory=s Base path on which project will be created [optional] --force Rewrite the model. [optional] --trace Shows the trace of the framework in case of exception. [optional] --mapcolumn Get some code for map columns. [optional] --abstract Abstract Model [optional] The simplest way to generate a model is:

$ phalcon model products

$ phalcon model --name tablename

All table fields are declared public for direct access.

/** * @var integer */ public $typesId;

/** * @var string

2.1. Installation 39 Phalcon PHP Framework Documentation, Release 3.1.1

*/ public $name;

/** * @var string */ public $price;

/** * @var integer */ public $quantity;

/** * @var string */ public $status; }

By adding the –get-set you can generate the fields with protected variables and public setter/getter methods. Those methods can help in business logic implementation within the setter/getter methods.

/** * @var integer */ protected $typesId;

/** * @var string */ protected $name;

/** * @var string */ protected $price;

/** * @var integer */ protected $quantity;

/** * @var string */ protected $status;

40 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

/** * Method to set the value of field id * * @param integer $id */ public function setId($id) { $this->id= $id; }

/** * Method to set the value of field typesId * * @param integer $typesId */ public function setTypesId($typesId) { $this->typesId= $typesId; }

// ...

/** * Returns the value of field status * * @return string */ public function getStatus() { return $this->status; } }

A nice feature of the model generator is that it keeps changes made by the developer between code generations. This allows the addition or removal of fields and properties, without worrying about losing changes made to the model itself. The following screencast shows you how it works:

Scaffold a CRUD

Scaffolding is a quick way to generate some of the major pieces of an application. If you want to create the models, views, and controllers for a new resource in a single operation, scaffolding is the tool for the job. Once the code is generated, it will have to be customized to meet your needs. Many developers avoid scaffolding entirely, opting to write all or most of their source code from scratch. The generated code can serve as a guide to better understand of how the framework works or develop prototypes. The code below shows a scaffold based on the table “products”:

$ phalcon scaffold --table-name products

The scaffold generator will build several files in your application, along with some folders. Here’s a quick overview of what will be generated:

2.1. Installation 41 Phalcon PHP Framework Documentation, Release 3.1.1

File Purpose app/controllers/ProductsController.php The Products controller app/models/Products.php The Products model app/views/layout/products.phtml Controller layout for Products app/views/products/new.phtml View for the action “new” app/views/products/edit.phtml View for the action “edit” app/views/products/search.phtml View for the action “search” When browsing the recently generated controller, you will see a search form and a link to create a new Product:

The “create page” allows you to create products applying validations on the Products model. Phalcon will automati- cally validate not null fields producing warnings if any of them is required. After performing a search, a pager component is available to show paged results. Use the “Edit” or “Delete” links in front of each result to perform such actions.

Web Interface to Tools

Also, if you prefer, it’s possible to use Phalcon Developer Tools from a web interface. Check out the following screencast to figure out how it works:

42 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

2.1. Installation 43 Phalcon PHP Framework Documentation, Release 3.1.1

44 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Integrating Tools with PhpStorm IDE

The screencast below shows how to integrate developer tools with the PhpStorm IDE. The configuration steps could be easily adapted to other IDEs for PHP.

Conclusion

Phalcon Developer Tools provides an easy way to generate code for your application, reducing development time and potential coding errors.

Tutorials

Tutorial 1: Let’s learn by example

Throughout this first tutorial, we’ll walk you through the creation of an application with a simple registration form from the ground up. We will also explain the basic aspects of the framework’s behavior. If you are interested in automatic code generation tools for Phalcon, you can check our developer tools. The best way to use this guide is to follow each step in turn. You can get the complete code here.

File structure

Phalcon does not impose a particular file structure for application development. Due to the fact that it is loosely coupled, you can implement Phalcon powered applications with a file structure you are most comfortable using. For the purposes of this tutorial and as a starting point, we suggest this very simple structure:

tutorial/ app/ controllers/ models/ views/ public/ css/ img/ js/

Note that you don’t need any “library” directory related to Phalcon. The framework is available in memory, ready for you to use. Before continuing, please be sure you’ve successfully installed Phalcon and have setup either Nginx, Apache or Chero- kee.

Bootstrap

The first file you need to create is the bootstrap file. This file is very important; since it serves as the base of your application, giving you control of all aspects of it. In this file you can implement initialization of components as well as application behavior. Ultimately, it is responsible for doing 3 things: 1. Setting up the autoloader. 2. Configuring the Dependency Injector.

2.2. Tutorials 45 Phalcon PHP Framework Documentation, Release 3.1.1

3. Handling the application request.

Autoloaders

The first part that we find in the bootstrap is registering an autoloader. This will be used to load classes as controllers and models in the application. For example we may register one or more directories of controllers increasing the flexibility of the application. In our example we have used the component Phalcon\Loader. With it, we can load classes using various strategies but for this example we have chosen to locate classes based on predefined directories:

use Phalcon\Loader;

// ...

$loader= new Loader();

$loader->registerDirs( [ "../app/controllers/", "../app/models/", ] );

$loader->register();

Dependency Management

A very important concept that must be understood when working with Phalcon is its dependency injection container. It may sound complex but is actually very simple and practical. A service container is a bag where we globally store the services that our application will use to function. Each time the framework requires a component, it will ask the container using an agreed upon name for the service. Since Phalcon is a highly decoupled framework, Phalcon\Di acts as glue facilitating the integration of the different components achieving their work together in a transparent manner.

use Phalcon\Di\FactoryDefault;

// ...

// Create a DI $di= new FactoryDefault();

Phalcon\Di\FactoryDefault is a variant of Phalcon\Di. To make things easier, it has registered most of the components that come with Phalcon. Thus we should not register them one by one. Later there will be no problem in replacing a factory service. In the next part, we register the “view” service indicating the directory where the framework will find the views files. As the views do not correspond to classes, they cannot be charged with an autoloader. Services can be registered in several ways, but for our tutorial we’ll use an anonymous function:

46 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

// ...

// Setup the view component $di->set( "view", function () { $view= new View();

$view->setViewsDir("../app/views/");

return $view; } );

Next we register a base URI so that all URIs generated by Phalcon include the “tutorial” folder we setup earlier. This will become important later on in this tutorial when we use the class Phalcon\Tag to generate a hyperlink.

// ...

// Setup a base URI so that all generated URIs include the "tutorial" folder $di->set( "url", function () { $url= new UrlProvider();

$url->setBaseUri("/tutorial/");

return $url; } );

Handling the application request

In the last part of this file, we find Phalcon\Mvc\Application. Its purpose is to initialize the request environment, route the incoming request, and then dispatch any discovered actions; it aggregates any responses and returns them when the process is complete.

// ...

$application= new Application($di);

$response= $application->handle();

$response->send();

2.2. Tutorials 47 Phalcon PHP Framework Documentation, Release 3.1.1

Putting everything together

The tutorial/public/index.php file should look like:

// Register an autoloader $loader= new Loader();

$loader->registerDirs( [ "../app/controllers/", "../app/models/", ] );

$loader->register();

// Create a DI $di= new FactoryDefault();

// Setup the view component $di->set( "view", function () { $view= new View();

$view->setViewsDir("../app/views/");

return $view; } );

// Setup a base URI so that all generated URIs include the "tutorial" folder $di->set( "url", function () { $url= new UrlProvider();

$url->setBaseUri("/tutorial/");

return $url; } );

48 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

$application= new Application($di); try { // Handle the request $response= $application->handle();

$response->send(); } catch (\Exception $e){ "Exception:", $e->getMessage(); }

As you can see, the bootstrap file is very short and we do not need to include any additional files. We have set ourselves a flexible MVC application in less than 30 lines of code.

Creating a Controller

By default Phalcon will look for a controller named “Index”. It is the starting point when no controller or action has been passed in the request. The index controller (app/controllers/IndexController.php) looks like:

Hello!"; } }

The controller classes must have the suffix “Controller” and controller actions must have the suffix “Action”. If you access the application from your browser, you should see something like this: Congratulations, you’re flying with Phalcon!

Sending output to a view

Sending output to the screen from the controller is at times necessary but not desirable as most purists in the MVC community will attest. Everything must be passed to the view that is responsible for outputting data on screen. Phalcon will look for a view with the same name as the last executed action inside a directory named as the last executed controller. In our case (app/views/index/index.phtml):

Hello!";

Our controller (app/controllers/IndexController.php) now has an empty action definition:

2.2. Tutorials 49 Phalcon PHP Framework Documentation, Release 3.1.1

} }

The browser output should remain the same. The Phalcon\Mvc\View static component is automatically created when the action execution has ended. Learn more about views usage here.

Designing a sign up form

Now we will change the index.phtml view file, to add a link to a new controller named “signup”. The goal is to allow users to sign up within our application.

Hello!"; echo PHP_EOL; echo PHP_EOL; echo $this->tag->linkTo( "signup", "Sign Up Here!" );

The generated HTML code displays an anchor (“a”) HTML tag linking to a new controller:

50 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Hello!

Sign Up Here!

To generate the tag we use the class Phalcon\Tag. This is a utility class that allows us to build HTML tags with framework conventions in mind. As this class is a also a service registered in the DI we use $this->tag to access it. A more detailed article regarding HTML generation can be found here.

Here is the Signup controller (app/controllers/SignupController.php):

use Phalcon\Mvc\Controller;

class SignupController extends Controller { public function indexAction() {

} }

The empty index action gives the clean pass to a view with the form definition (app/views/signup/index.phtml):

Sign up using this form

2.2. Tutorials 51 Phalcon PHP Framework Documentation, Release 3.1.1

tag->form("signup/register"); ?>

tag->textField("name"); ?>

tag->textField("email"); ?>

tag->submitButton("Register"); ?>

Viewing the form in your browser will show something like this:

Phalcon\Tag also provides useful methods to build form elements.

52 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

The Phalcon\Tag::form() method receives only one parameter for instance, a relative URI to a controller/action in the application. By clicking the “Send” button, you will notice an exception thrown from the framework, indicating that we are missing the “register” action in the controller “signup”. Our public/index.php file throws this exception: Exception: Action “register” was not found on handler “signup” Implementing that method will remove the exception:

}

public function registerAction() {

} }

If you click the “Send” button again, you will see a blank page. The name and email input provided by the user should be stored in a database. According to MVC guidelines, database interactions must be done through models so as to ensure clean object-oriented code.

Creating a Model

Phalcon brings the first ORM for PHP entirely written in C-language. Instead of increasing the complexity of devel- opment, it simplifies it. Before creating our first model, we need to create a database table outside of Phalcon to map it to. A simple table to store registered users can be defined like this:

CREATE TABLE `users`( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(70) NOT NULL, `email` varchar(70) NOT NULL,

PRIMARY KEY (`id`) );

A model should be located in the app/models directory (app/models/Users.php). The model maps to the “users” table:

public $name;

2.2. Tutorials 53 Phalcon PHP Framework Documentation, Release 3.1.1

public $email; }

Setting a Database Connection

In order to be able to use a database connection and subsequently access data through our models, we need to specify it in our bootstrap process. A database connection is just another service that our application has that can be used for several components:

// Setup the database service $di->set( "db", function () { return new DbAdapter( [ "host" =>"localhost", "username" =>"root", "password" =>"secret", "dbname" =>"test_db", ] ); } );

With the correct database parameters, our models are ready to work and interact with the rest of the application.

Storing data using models

Receiving data from the form and storing them in the table is the next step.

}

public function registerAction() { $user= new Users();

// Store and check for errors $success= $user->save( $this->request->getPost(), [ "name",

54 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

"email", ] );

if ($success){ echo "Thanks for registering!"; } else { echo "Sorry, the following problems were generated:";

$messages= $user->getMessages();

foreach ($messages as $message){ echo $message->getMessage(),"
"; } }

$this->view->disable(); } }

We then instantiate the Users class, which corresponds to a User record. The class public properties map to the fields of the record in the users table. Setting the relevant values in the new record and calling save() will store the data in the database for that record. The save() method returns a boolean value which indicates whether the storing of the data was successful or not. The ORM automatically escapes the input preventing SQL injections so we only need to pass the request to the save() method. Additional validation happens automatically on fields that are defined as not null (required). If we don’t enter any of the required fields in the sign up form our screen will look like this:

2.2. Tutorials 55 Phalcon PHP Framework Documentation, Release 3.1.1

Conclusion

This is a very simple tutorial and as you can see, it’s easy to start building an application using Phalcon. The fact that Phalcon is an extension on your web server has not interfered with the ease of development or features available. We invite you to continue reading the manual so that you can discover additional features offered by Phalcon!

Tutorial 2: Introducing INVO

In this second tutorial, we’ll explain a more complete application in order to gain a deeper understanding of developing with Phalcon. INVO is one of the sample applications we have created. INVO is a small website that allows users to generate invoices and do other tasks such as manage customers and products. You can clone its code from Github. INVO was made with the client-side framework Bootstrap. Although the application does not generate actual invoices, it still serves as an example showing how the framework works.

Project Structure

Once you clone the project in your document root you’ll see the following structure: invo/ app/ config/ controllers/ forms/ library/ logs/ models/ plugins/ views/ cache/ volt/ docs/ public/ css/ fonts/ js/ schemas/

As you know, Phalcon does not impose a particular file structure for application development. This project has a simple MVC structure and a public document root. Once you open the application in your browser http://localhost/invo you’ll see something like this: The application is divided into two parts: a frontend and a backend. The frontend is a public area where visitors can receive information about INVO and request contact information. The backend is an administrative area where registered users can manage their products and customers.

Routing

INVO uses the standard route that is built-in with the Router component. These routes match the following pattern: /:controller/:action/:params. This means that the first part of a URI is the controller, the second the controller action and the rest are the parameters. The following route /session/register executes the controller SessionController and its action registerAction.

56 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Configuration

INVO has a configuration file that sets general parameters in the application. This file is located at app/config/config.ini and is loaded in the very first lines of the application bootstrap (public/index.php):

// ...

// Read the configuration $config= new ConfigIni( APP_PATH."app/config/config.ini" );

Phalcon\Config allows us to manipulate the file in an object-oriented way. In this example, we’re using an ini file for configuration but Phalcon has adapters for other file types as well. The configuration file contains the following settings:

[database] host= localhost username= root password= secret name= invo

[application] controllersDir= app/controllers/ modelsDir= app/models/ viewsDir= app/views/

2.2. Tutorials 57 Phalcon PHP Framework Documentation, Release 3.1.1

pluginsDir= app/plugins/ formsDir= app/forms/ libraryDir= app/library/ baseUri= /invo/

Phalcon doesn’t have any pre-defined settings convention. Sections help us to organize the options as appropriate. In this file there are two sections to be used later: “application” and “database”.

Autoloaders

The second part that appears in the bootstrap file (public/index.php) is the autoloader:

/** * Auto-loader configuration */ require APP_PATH."app/config/loader.php";

The autoloader registers a set of directories in which the application will look for the classes that it will eventually need.

$loader= new Phalcon\Loader();

// We're a registering a set of directories taken from the configuration file $loader->registerDirs( [ APP_PATH. $config->application->controllersDir, APP_PATH. $config->application->pluginsDir, APP_PATH. $config->application->libraryDir, APP_PATH. $config->application->modelsDir, APP_PATH. $config->application->formsDir, ] );

$loader->register();

Note that the above code has registered the directories that were defined in the configuration file. The only directory that is not registered is the viewsDir because it contains HTML + PHP files but no classes. Also, note that we use a constant called APP_PATH. This constant is defined in the bootstrap (public/index.php) to allow us to have a reference to the root of our project:

// ... define( "APP_PATH", realpath("..")."/" );

58 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Registering services

Another file that is required in the bootstrap is (app/config/services.php). This file allows us to organize the services that INVO uses.

/** * Load application services */ require APP_PATH."app/config/services.php";

Service registration is achieved as in the previous tutorial, making use of closures to lazily load the required compo- nents:

use Phalcon\Mvc\Url as UrlProvider;

// ...

/** * The URL component is used to generate all kind of URLs in the application */ $di->set( "url", function () use ($config){ $url= new UrlProvider();

$url->setBaseUri( $config->application->baseUri );

return $url; } );

We will discuss this file in depth later.

Handling the Request

If we skip to the end of the file (public/index.php), the request is finally handled by Phalcon\Mvc\Application which initializes and executes all that is necessary to make the application run:

use Phalcon\Mvc\Application;

// ...

$application= new Application($di);

$response= $application->handle();

$response->send();

2.2. Tutorials 59 Phalcon PHP Framework Documentation, Release 3.1.1

Dependency Injection

In the first line of the code block above, the Application class constructor is receiving the variable $di as an argument. What is the purpose of that variable? Phalcon is a highly decoupled framework so we need a component that acts as glue to make everything work together. That component is Phalcon\Di. It’s a service container that also performs dependency injection and service location, instantiating all components as they are needed by the application. There are many ways of registering services in the container. In INVO, most services have been registered using anonymous functions/closures. Thanks to this, the objects are instantiated in a lazy way, reducing the resources needed by the application. For instance, in the following excerpt the session service is registered. The anonymous function will only be called when the application requires access to the session data:

// ...

// Start the session the first time a component requests the session service $di->set( "session", function () { $session= new Session();

$session->start();

return $session; } );

Here, we have the freedom to change the adapter, perform additional initialization and much more. Note that the service was registered using the name “session”. This is a convention that will allow the framework to identify the active service in the services container. A request can use many services and registering each service individually can be a cumbersome task. For that reason, the framework provides a variant of Phalcon\Di called Phalcon\Di\FactoryDefault whose task is to register all services providing a full-stack framework.

// ...

// The FactoryDefault Dependency Injector automatically registers the // right services providing a full-stack framework $di= new FactoryDefault();

It registers the majority of services with components provided by the framework as standard. If we need to override the definition of some service we could just set it again as we did above with “session” or “url”. This is the reason for the existence of the variable $di. In next chapter, we will see how authentication and authorization is implemented in INVO.

60 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Tutorial 3: Securing INVO

In this chapter, we continue explaining how INVO is structured, we’ll talk about the implementation of authentication, authorization using events and plugins and an access control list (ACL) managed by Phalcon.

Log into the Application

A “log in” facility will allow us to work on backend controllers. The separation between backend controllers and frontend ones is only logical. All controllers are located in the same directory (app/controllers/). To enter the system, users must have a valid username and password. Users are stored in the table “users” in the database “invo”. Before we can start a session, we need to configure the connection to the database in the application. A service called “db” is set up in the service container with the connection information. As with the autoloader, we are again taking parameters from the configuration file in order to configure a service:

// ...

// Database connection is created based on parameters defined in the configuration

˓→file $di->set( "db", function () use ($config){ return new DbAdapter( [ "host" => $config->database->host, "username" => $config->database->username, "password" => $config->database->password, "dbname" => $config->database->name, ] ); } );

Here, we return an instance of the MySQL connection adapter. If needed, you could do extra actions such as adding a logger, a profiler or change the adapter, setting it up as you want. The following simple form (app/views/session/index.volt) requests the login information. We’ve removed some HTML code to make the example more concise:

{{ form("session/start") }}

{{ text_field("email") }}

2.2. Tutorials 61 Phalcon PHP Framework Documentation, Release 3.1.1

{{ password_field("password") }}

{{ submit_button("Login") }}
{{ endForm() }}

Instead of using raw PHP as the previous tutorial, we started to use Volt. This is a built-in template engine inspired in providing a simpler and friendly syntax to create templates. It will not take too long before you become familiar with Volt. The SessionController::startAction function (app/controllers/SessionController.php) has the task of val- idating the data entered in the form including checking for a valid user in the database:

private function _registerSession($user) { $this->session->set( "auth", [ "id" => $user->id, "name" => $user->name, ] ); }

/** * This action authenticate and logs a user into the application */ public function startAction() { if ($this->request->isPost()) { // Get the data from the user $email= $this->request->getPost("email"); $password= $this->request->getPost("password");

// Find the user in the database $user= Users::findFirst( [ "(email = :email: OR username = :email:) AND password =

˓→:password: AND active = 'Y'", "bind" =>[ "email" => $email,

62 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

"password" => sha1($password), ] ] );

if ($user !== false){ $this->_registerSession($user);

$this->flash->success( "Welcome". $user->name );

// Forward to the 'invoices' controller if the user is valid return $this->dispatcher->forward( [ "controller" =>"invoices", "action" =>"index", ] ); }

$this->flash->error( "Wrong email/password" ); }

// Forward to the login form again return $this->dispatcher->forward( [ "controller" =>"session", "action" =>"index", ] ); } }

For the sake of simplicity, we have used “sha1” to store the password hashes in the database, however, this algorithm is not recommended in real applications, use “bcrypt” instead. Note that multiple public attributes are accessed in the controller like: $this->flash, $this->request or $this->session. These are services defined in the services container from earlier (app/config/services.php). When they’re accessed the first time, they are injected as part of the controller. These services are “shared”, which means that we are always accessing the same instance regardless of the place where we invoke them. For instance, here we invoke the “session” service and then we store the user identity in the variable “auth”:

$this->session->set( "auth", [ "id" => $user->id, "name" => $user->name, ] );

Another important aspect of this section is how the user is validated as a valid one, first we validate whether the request

2.2. Tutorials 63 Phalcon PHP Framework Documentation, Release 3.1.1 has been made using method POST:

request->isPost()) {

Then, we receive the parameters from the form:

$email= $this->request->getPost("email"); $password= $this->request->getPost("password");

Now, we have to check if there is one user with the same username or email and password:

$user= Users::findFirst( [ "(email = :email: OR username = :email:) AND password = :password: AND active

˓→= 'Y'", "bind" =>[ "email" => $email, "password" => sha1($password), ] ] );

Note, the use of ‘bound parameters’, placeholders :email: and :password: are placed where values should be, then the values are ‘bound’ using the parameter ‘bind’. This safely replaces the values for those columns without having the risk of a SQL injection. If the user is valid we register it in session and forwards him/her to the dashboard:

_registerSession($user);

$this->flash->success( "Welcome". $user->name );

return $this->dispatcher->forward( [ "controller" =>"invoices", "action" =>"index", ] ); }

If the user does not exist we forward the user back again to action where the form is displayed:

dispatcher->forward( [ "controller" =>"session", "action" =>"index",

64 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

] );

Securing the Backend

The backend is a private area where only registered users have access. Therefore, it is necessary to check that only registered users have access to these controllers. If you aren’t logged into the application and you try to access, for example, the products controller (which is private) you will see a screen like this:

Every time someone attempts to access any controller/action, the application verifies that the current role (in session) has access to it, otherwise it displays a message like the above and forwards the flow to the home page. Now let’s find out how the application accomplishes this. The first thing to know is that there is a component called Dispatcher. It is informed about the route found by the Routing component. Then, it is responsible for loading the appropriate controller and execute the corresponding action method. Normally, the framework creates the Dispatcher automatically. In our case, we want to perform a verification before executing the required action, checking if the user has access to it or not. To achieve this, we have replaced the component by creating a function in the bootstrap:

// ...

/** * MVC dispatcher

2.2. Tutorials 65 Phalcon PHP Framework Documentation, Release 3.1.1

*/ $di->set( "dispatcher", function () { // ...

$dispatcher= new Dispatcher();

return $dispatcher; } );

We now have total control over the Dispatcher used in the application. Many components in the framework trigger events that allow us to modify their internal flow of operation. As the Dependency Injector component acts as glue for components, a new component called EventsManager allows us to intercept the events produced by a component, routing the events to listeners.

Events Management

An EventsManager allows us to attach listeners to a particular type of event. The type that interests us now is “dis- patch”. The following code filters all events produced by the Dispatcher:

use Phalcon\Mvc\Dispatcher; use Phalcon\Events\Manager as EventsManager;

$di->set( "dispatcher", function () { // Create an events manager $eventsManager= new EventsManager();

// Listen for events produced in the dispatcher using the Security plugin $eventsManager->attach( "dispatch:beforeExecuteRoute", new SecurityPlugin() );

// Handle exceptions and not-found exceptions using NotFoundPlugin $eventsManager->attach( "dispatch:beforeException", new NotFoundPlugin() );

$dispatcher= new Dispatcher();

// Assign the events manager to the dispatcher $dispatcher->setEventsManager($eventsManager);

return $dispatcher; } );

When an event called “beforeExecuteRoute” is triggered the following plugin will be notified:

66 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

/** * Check if the user is allowed to access certain action using the SecurityPlugin */ $eventsManager->attach( "dispatch:beforeExecuteRoute", new SecurityPlugin() );

When a “beforeException” is triggered then other plugin is notified:

/** * Handle exceptions and not-found exceptions using NotFoundPlugin */ $eventsManager->attach( "dispatch:beforeException", new NotFoundPlugin() );

SecurityPlugin is a class located at (app/plugins/SecurityPlugin.php). This class implements the method “beforeExe- cuteRoute”. This is the same name as one of the events produced in the Dispatcher:

use Phalcon\Events\Event; use Phalcon\Mvc\User\Plugin; use Phalcon\Mvc\Dispatcher;

class SecurityPlugin extends Plugin { // ...

public function beforeExecuteRoute(Event $event, Dispatcher $dispatcher) { // ... } }

The hook events always receive a first parameter that contains contextual information of the event produced ($event) and a second one that is the object that produced the event itself ($dispatcher). It is not mandatory that plugins extend the class Phalcon\Mvc\User\Plugin, but by doing this they gain easier access to the services available in the application. Now, we’re verifying the role in the current session, checking if the user has access using the ACL list. If the user does not have access we redirect to the home screen as explained before:

use Phalcon\Acl; use Phalcon\Events\Event; use Phalcon\Mvc\User\Plugin; use Phalcon\Mvc\Dispatcher;

class SecurityPlugin extends Plugin {

2.2. Tutorials 67 Phalcon PHP Framework Documentation, Release 3.1.1

// ...

public function beforeExecuteRoute(Event $event, Dispatcher $dispatcher) { // Check whether the "auth" variable exists in session to define the active

˓→role $auth= $this->session->get("auth");

if (!$auth){ $role="Guests"; } else { $role="Users"; }

// Take the active controller/action from the dispatcher $controller= $dispatcher->getControllerName(); $action= $dispatcher->getActionName();

// Obtain the ACL list $acl= $this->getAcl();

// Check if the Role have access to the controller (resource) $allowed= $acl->isAllowed($role, $controller, $action);

if (!$allowed){ // If he doesn't have access forward him to the index controller $this->flash->error( "You don't have access to this module" );

$dispatcher->forward( [ "controller" =>"index", "action" =>"index", ] );

// Returning "false" we tell to the dispatcher to stop the current

˓→operation return false; } } }

Providing an ACL list

In the above example we have obtained the ACL using the method $this->getAcl(). This method is also imple- mented in the Plugin. Now we are going to explain step-by-step how we built the access control list (ACL):

// Create the ACL

68 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

$acl= new AclList();

// The default action is DENY access $acl->setDefaultAction( Acl::DENY );

// Register two roles, Users is registered users // and guests are users without a defined identity $roles=[ "users" => new Role("Users"), "guests" => new Role("Guests"), ]; foreach ($roles as $role){ $acl->addRole($role); }

Now, we define the resources for each area respectively. Controller names are resources and their actions are accesses for the resources:

// ...

// Private area resources (backend) $privateResources=[ "companies" =>["index","search","new","edit","save","create","delete"], "products" =>["index","search","new","edit","save","create","delete"], "producttypes" =>["index","search","new","edit","save","create","delete"], "invoices" =>["index","profile"], ]; foreach ($privateResources as $resourceName => $actions){ $acl->addResource( new Resource($resourceName), $actions ); }

// Public area resources (frontend) $publicResources=[ "index" =>["index"], "about" =>["index"], "register" =>["index"], "errors" =>["show404","show500"], "session" =>["index","register","start","end"], "contact" =>["index","send"], ]; foreach ($publicResources as $resourceName => $actions){ $acl->addResource( new Resource($resourceName), $actions

2.2. Tutorials 69 Phalcon PHP Framework Documentation, Release 3.1.1

); }

The ACL now have knowledge of the existing controllers and their related actions. Role “Users” has access to all the resources of both frontend and backend. The role “Guests” only has access to the public area:

// Grant access to public areas to both users and guests foreach ($roles as $role){ foreach ($publicResources as $resource => $actions){ $acl->allow( $role->getName(), $resource, "*" ); } }

// Grant access to private area only to role Users foreach ($privateResources as $resource => $actions){ foreach ($actions as $action){ $acl->allow( "Users", $resource, $action ); } }

Hooray!, the ACL is now complete. In next chapter, we will see how a CRUD is implemented in Phalcon and how you can customize it.

Tutorial 4: Working with the CRUD

Backends usually provide forms to allow users to manipulate data. Continuing the explanation of INVO, we now ad- dress the creation of CRUDs, a very common task that Phalcon will facilitate you using forms, validations, paginators and more. Most options that manipulate data in INVO (companies, products and types of products) were developed using a basic and common CRUD (Create, Read, Update and Delete). Each CRUD contains the following files: invo/ app/ controllers/ ProductsController.php models/ Products.php forms/ ProductsForm.php views/ products/ edit.volt index.volt new.volt search.volt

70 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Each controller has the following actions:

/** * Execute the "search" based on the criteria sent from the "index" * Returning a paginator for the results */ public function searchAction() { // ... }

/** * Shows the view to create a "new" product */ public function newAction() { // ... }

/** * Shows the view to "edit" an existing product */ public function editAction() { // ... }

/** * Creates a product based on the data entered in the "new" action */ public function createAction() { // ... }

/** * Updates a product based on the data entered in the "edit" action */ public function saveAction() { // ... }

/** * Deletes an existing product */ public function deleteAction($id)

2.2. Tutorials 71 Phalcon PHP Framework Documentation, Release 3.1.1

{ // ... } }

The Search Form

Every CRUD starts with a search form. This form shows each field that the table has (products), allowing the user to create a search criteria for any field. The “products” table has a relationship with the table “products_types”. In this case, we previously queried the records in this table in order to facilitate the search by that field:

/** * The start action, it shows the "search" view */ public function indexAction() { $this->persistent->searchParams= null;

$this->view->form= new ProductsForm(); }

An instance of the ProductsForm form (app/forms/ProductsForm.php) is passed to the view. This form defines the fields that are visible to the user:

$element->setLabel("Id");

$this->add( $element ); } else { $this->add( new Hidden("id") ); }

72 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

$name= new Text("name");

$name->setLabel("Name");

$name->setFilters( [ "striptags", "string", ] );

$name->addValidators( [ new PresenceOf( [ "message" =>"Name is required", ] ) ] );

$this->add($name);

$type= new Select( "profilesId", ProductTypes::find(), [ "using" =>[ "id", "name", ], "useEmpty" => true, "emptyText" =>"...", "emptyValue" =>"", ] );

$this->add($type);

$price= new Text("price");

$price->setLabel("Price");

$price->setFilters( [ "float", ] );

$price->addValidators( [

2.2. Tutorials 73 Phalcon PHP Framework Documentation, Release 3.1.1

new PresenceOf( [ "message" =>"Price is required", ] ), new Numericality( [ "message" =>"Price is required", ] ), ] );

$this->add($price); } }

The form is declared using an object-oriented scheme based on the elements provided by the forms component. Every element follows almost the same structure:

// Create the element $name= new Text("name");

// Set its label $name->setLabel("Name");

// Before validating the element apply these filters $name->setFilters( [ "striptags", "string", ] );

// Apply this validators $name->addValidators( [ new PresenceOf( [ "message" =>"Name is required", ] ) ] );

// Add the element to the form $this->add($name);

Other elements are also used in this form:

// Add a hidden input to the form $this->add( new Hidden("id") );

74 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

// ...

$productTypes= ProductTypes::find();

// Add a HTML Select (list) to the form // and fill it with data from "product_types" $type= new Select( "profilesId", $productTypes, [ "using" =>[ "id", "name", ], "useEmpty" => true, "emptyText" =>"...", "emptyValue" =>"", ] );

Note that ProductTypes::find() contains the data necessary to fill the SELECT tag using Phalcon\Tag::select(). Once the form is passed to the view, it can be rendered and presented to the user:

{{ form("products/search") }}

Search products

{% for element in form %}

{{ element.label(["class": "control-label"]) }}

{{ element }}
{% endfor %}

{{ submit_button("Search", "class": "btn btn-primary") }}

{{ endForm() }}

This produces the following HTML:

2.2. Tutorials 75 Phalcon PHP Framework Documentation, Release 3.1.1

Search products

When the form is submitted, the “search” action is executed in the controller performing the search based on the data entered by the user.

76 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Performing a Search

The “search” action has two behaviors. When accessed via POST, it performs a search based on the data sent from the form but when accessed via GET it moves the current page in the paginator. To differentiate HTTP methods, we check it using the Request component:

/** * Execute the "search" based on the criteria sent from the "index" * Returning a paginator for the results */ public function searchAction() { if ($this->request->isPost()) { // Create the query conditions } else { // Paginate using the existing conditions }

// ... }

With the help of Phalcon\Mvc\Model\Criteria, we can create the search conditions intelligently based on the data types and values sent from the form:

$query= Criteria::fromInput( $this->di, "Products", $this->request->getPost() );

This method verifies which values are different from “” (empty string) and null and takes them into account to create the search criteria: • If the field data type is text or similar (char, varchar, text, etc.) It uses an SQL “like” operator to filter the results. • If the data type is not text or similar, it’ll use the operator “=”. Additionally, “Criteria” ignores all the $_POST variables that do not match any field in the table. Values are automat- ically escaped using “bound parameters”. Now, we store the produced parameters in the controller’s session bag:

$this->persistent->searchParams= $query->getParams();

A session bag, is a special attribute in a controller that persists between requests using the session service. When accessed, this attribute injects a Phalcon\Session\Bag instance that is independent in each controller. Then, based on the built params we perform the query:

$products= Products::find($parameters); if (count($products) ===0){

2.2. Tutorials 77 Phalcon PHP Framework Documentation, Release 3.1.1

$this->flash->notice( "The search did not found any products" );

return $this->dispatcher->forward( [ "controller" =>"products", "action" =>"index", ] ); }

If the search doesn’t return any product, we forward the user to the index action again. Let’s pretend the search returned results, then we create a paginator to navigate easily through them:

// ...

$paginator= new Paginator( [ "data" => $products, // Data to paginate "limit" =>5, // Rows per page "page" => $numberPage, // Active page ] );

// Get active page in the paginator $page= $paginator->getPaginate();

Finally we pass the returned page to view:

$this->view->page= $page;

In the view (app/views/products/search.volt), we traverse the results corresponding to the current page, showing every row in the current page to the user:

{% for product in page.items %} {% if loop.first %}

{% endif %}

{% if loop.last %}

Id Product Type Name Price Active

78 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

{{ product.id }}

{{ product.getProductTypes().name }} {{ product.name }} {{ "%.2f"|format(product.price) }} {{ product.getActiveDetail() }} {{ link_to("products/edit/"~ product.id, "Edit") }} {{ link_to("products/delete/"~ product.id, "Delete") }}
{{ link_to("products/search", "First") }} {{ link_to("products/search?page="~ page.before,

˓→"Previous") }} {{ link_to("products/search?page="~ page.next, "Next") }} {{ link_to("products/search?page="~ page.last, "Last") }} {{ page.current }} of {{ page.

˓→total_pages }}

{% endif %} {% else %} No products are recorded {% endfor %}

There are many things in the above example that worth detailing. First of all, active items in the current page are traversed using a Volt’s ‘for’. Volt provides a simpler syntax for a PHP ‘foreach’.

{% for product in page.items %}

Which in PHP is the same as:

2.2. Tutorials 79 Phalcon PHP Framework Documentation, Release 3.1.1

items as $product){ ?>

The whole ‘for’ block provides the following:

{% for product in page.items %} {% if loop.first %} Executed before the first product in the loop {% endif %}

Executed for every product of page.items

{% if loop.last %} Executed after the last product is loop {% endif %} {% else %} Executed if page.items does not have any products {% endfor %}

Now you can go back to the view and find out what every block is doing. Every field in “product” is printed accord- ingly:

{{ product.id }}

{{ product.productTypes.name }}

{{ product.name }}

{{ "%.2f"|format(product.price) }}

{{ product.getActiveDetail() }}

{{ link_to("products/edit/"~ product.id, "Edit") }}

{{ link_to("products/delete/"~ product.id, "Delete") }}

As we seen before using product.id is the same as in PHP as doing: $product->id, we made the same with product.name and so on. Other fields are rendered differently, for instance, let’s focus in product. productTypes.name. To understand this part, we have to check the Products model (app/models/Products.php):

80 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

/** * Products */ class Products extends Model { // ...

/** * Products initializer */ public function initialize() { $this->belongsTo( "product_types_id", "ProductTypes", "id", [ "reusable" => true, ] ); }

// ... }

A model can have a method called initialize(), this method is called once per request and it serves the ORM to initialize a model. In this case, “Products” is initialized by defining that this model has a one-to-many relationship to another model called “ProductTypes”.

$this->belongsTo( "product_types_id", "ProductTypes", "id", [ "reusable" => true, ] );

Which means, the local attribute “product_types_id” in “Products” has an one-to-many relation to the “ProductTypes” model in its attribute “id”. By defining this relationship we can access the name of the product type by using:

{{ product.productTypes.name }}

The field “price” is printed by its formatted using a Volt filter:

{{ "%.2f"|format(product.price) }}

In plain PHP, this would be:

price) ?>

Printing whether the product is active or not uses a helper implemented in the model:

2.2. Tutorials 81 Phalcon PHP Framework Documentation, Release 3.1.1

{{ product.getActiveDetail() }}

This method is defined in the model.

Creating and Updating Records

Now let’s see how the CRUD creates and updates records. From the “new” and “edit” views, the data entered by the user is sent to the “create” and “save” actions that perform actions of “creating” and “updating” products, respectively. In the creation case, we recover the data submitted and assign them to a new “Products” instance:

/** * Creates a product based on the data entered in the "new" action */ public function createAction() { if (!$this->request->isPost()) { return $this->dispatcher->forward( [ "controller" =>"products", "action" =>"index", ] ); }

$form= new ProductsForm();

$product= new Products();

$product->id= $this->request->getPost("id","int"); $product->product_types_id= $this->request->getPost("product_types_id","int"); $product->name= $this->request->getPost("name","striptags"); $product->price= $this->request->getPost("price","double"); $product->active= $this->request->getPost("active");

// ... }

Remember the filters we defined in the Products form? Data is filtered before being assigned to the object $product. This filtering is optional; the ORM also escapes the input data and performs additional casting according to the column types:

// ...

$name= new Text("name");

$name->setLabel("Name");

// Filters for name $name->setFilters( [ "striptags", "string",

82 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

] );

// Validators for name $name->addValidators( [ new PresenceOf( [ "message" =>"Name is required", ] ) ] );

$this->add($name);

When saving, we’ll know whether the data conforms to the business rules and validations implemented in the form ProductsForm form (app/forms/ProductsForm.php):

// ...

$form= new ProductsForm();

$product= new Products();

// Validate the input $data= $this->request->getPost(); if (!$form->isValid($data, $product)) { $messages= $form->getMessages();

foreach ($messages as $message){ $this->flash->error($message); }

return $this->dispatcher->forward( [ "controller" =>"products", "action" =>"new", ] ); }

Finally, if the form does not return any validation message we can save the product instance:

// ... if ($product->save() === false){ $messages= $product->getMessages();

foreach ($messages as $message){ $this->flash->error($message); }

2.2. Tutorials 83 Phalcon PHP Framework Documentation, Release 3.1.1

return $this->dispatcher->forward( [ "controller" =>"products", "action" =>"new", ] ); }

$form->clear();

$this->flash->success( "Product was created successfully" ); return $this->dispatcher->forward( [ "controller" =>"products", "action" =>"index", ] );

Now, in the case of updating a product, we must first present the user with the data that is currently in the edited record:

/** * Edits a product based on its id */ public function editAction($id) { if (!$this->request->isPost()) { $product= Products::findFirstById($id);

if (!$product){ $this->flash->error( "Product was not found" );

return $this->dispatcher->forward( [ "controller" =>"products", "action" =>"index", ] ); }

$this->view->form= new ProductsForm( $product, [ "edit" => true, ] ); } }

The data found is bound to the form by passing the model as first parameter. Thanks to this, the user can change any value and then sent it back to the database through to the “save” action:

84 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

/** * Updates a product based on the data entered in the "edit" action */ public function saveAction() { if (!$this->request->isPost()) { return $this->dispatcher->forward( [ "controller" =>"products", "action" =>"index", ] ); }

$id= $this->request->getPost("id","int");

$product= Products::findFirstById($id);

if (!$product){ $this->flash->error( "Product does not exist" );

return $this->dispatcher->forward( [ "controller" =>"products", "action" =>"index", ] ); }

$form= new ProductsForm();

$data= $this->request->getPost();

if (!$form->isValid($data, $product)) { $messages= $form->getMessages();

foreach ($messages as $message){ $this->flash->error($message); }

return $this->dispatcher->forward( [ "controller" =>"products", "action" =>"new", ] ); }

if ($product->save() === false){ $messages= $product->getMessages();

foreach ($messages as $message){ $this->flash->error($message); }

2.2. Tutorials 85 Phalcon PHP Framework Documentation, Release 3.1.1

return $this->dispatcher->forward( [ "controller" =>"products", "action" =>"new", ] ); }

$form->clear();

$this->flash->success( "Product was updated successfully" );

return $this->dispatcher->forward( [ "controller" =>"products", "action" =>"index", ] ); }

We have seen how Phalcon lets you create forms and bind data from a database in a structured way. In next chapter, we will see how to add custom HTML elements like a menu.

Tutorial 5: Customizing INVO

To finish the detailed explanation of INVO we are going to explain how to customize INVO adding UI elements and changing the title according to the controller executed.

User Components

All the UI elements and visual style of the application has been achieved mostly through Bootstrap. Some elements, such as the navigation bar changes according to the state of the application. For example, in the upper right corner, the link “Log in / Sign Up” changes to “Log out” if a user is logged into the application. This part of the application is implemented in the component “Elements” (app/library/Elements.php).

use Phalcon\Mvc\User\Component;

class Elements extends Component { public function getMenu() { // ... }

public function getTabs() { // ... } }

86 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

This class extends the Phalcon\Mvc\User\Component. It is not imposed to extend a component with this class, but it helps to get access more quickly to the application services. Now, we are going to register our first user component in the services container:

// Register a user component $di->set( "elements", function () { return new Elements(); } );

As controllers, plugins or components within a view, this component also has access to the services registered in the container and by just accessing an attribute with the same name as a previously registered service:

{{ content() }}


© Company 2015

The important part is:

{{ elements.getMenu() }}

Changing the Title Dynamically

When you browse between one option and another will see that the title changes dynamically indicating where we are currently working. This is achieved in each controller initializer:

class ProductsController extends ControllerBase {

2.2. Tutorials 87 Phalcon PHP Framework Documentation, Release 3.1.1

public function initialize() { // Set the document title $this->tag->setTitle( "Manage your product types" );

parent::initialize(); }

// ... }

Note, that the method parent::initialize() is also called, it adds more data to the title:

use Phalcon\Mvc\Controller;

class ControllerBase extends Controller { protected function initialize() { // Prepend the application name to the title $this->tag->prependTitle( "INVO |" ); }

// ... }

Finally, the title is printed in the main view (app/views/index.volt):

tag->getTitle(); ?>

Tutorial 6: Vökuró

Vökuró is another sample application you can use to learn more about Phalcon. Vökuró is a small website that shows how to implement a security features and management of users and permissions. You can clone its code from Github.

Project Structure

Once you clone the project in your document root you’ll see the following structure:

vokuro/ app/ config/

88 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

controllers/ forms/ library/ models/ views/ cache/ public/ css/ img/ schemas/

This project follows a quite similar structure to INVO. Once you open the application in your browser http://localhost/ vokuro you’ll see something like this:

The application is divided into two parts, a frontend, where visitors can sign up the service and a backend where administrative users can manage registered users. Both frontend and backend are combined in a single module.

Load Classes and Dependencies

This project uses Phalcon\Loader to load controllers, models, forms, etc. within the project and to load the project’s dependencies. So, the first thing you have to do before execute Vökuró is install its dependencies via composer. Assuming you have it correctly installed, type the following command in the console: cd vokuro composer install

Vökuró sends emails to confirm the sign up of registered users using Swift, the composer.json looks like:

2.2. Tutorials 89 Phalcon PHP Framework Documentation, Release 3.1.1

{ "require" :{ "php" : ">=5.5.0", "ext-phalcon" : ">=3.0.0", "swiftmailer/swiftmailer" : "^5.4", "amazonwebservices/aws-sdk-for-php" : "~1.0" } }

Now, there is a file called app/config/loader.php where all the auto-loading stuff is set up. At the end of this file you can see that the composer autoloader is included enabling the application to autoload any of the classes in the downloaded dependencies:

// ...

// Use composer autoloader to load vendor classes require_once BASE_PATH."/vendor/autoload.php";

Moreover, Vökuró, unlike the INVO, utilizes namespaces for controllers and models which is the recommended practice to structure a project. This way the autoloader looks slightly different than the one we saw before (app/config/loader.php):

use Phalcon\Loader;

$loader= new Loader();

$loader->registerNamespaces( [ "Vokuro\\Models" => $config->application->modelsDir, "Vokuro\\Controllers" => $config->application->controllersDir, "Vokuro\\Forms" => $config->application->formsDir, "Vokuro" => $config->application->libraryDir, ] );

$loader->register();

// ...

Instead of using registerDirectories(), we use registerNamespaces(). Every namespace points to a directory defined in the configuration file (app/config/config.php). For instance the namespace Vokuro\Controllers points to app/controllers so all the classes required by the application within this namespace requires it in its definition:

namespace Vokuro\Controllers;

class AboutController extends ControllerBase { // ... }

90 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Sign Up

First, let’s check how users are registered in Vökuró. When a user clicks the “Create an Account” button, the controller SessionController is invoked and the action “signup” is executed:

// ...

$this->view->form= $form; } }

This action simply pass a form instance of SignUpForm to the view, which itself is rendered to allow the user enter the login details:

{{ form("class": "form-search") }}

Sign Up

{{ form.label("name") }}

{{ form.render("name") }} {{ form.messages("name") }}

{{ form.label("email") }}

{{ form.render("email") }} {{ form.messages("email") }}

{{ form.label("password") }}

{{ form.render("password") }} {{ form.messages("password") }}

{{ form.label("confirmPassword") }}

{{ form.render("confirmPassword") }} {{ form.messages("confirmPassword") }}

{{ form.render("terms") }}{{ form.label("terms") }}

2.2. Tutorials 91 Phalcon PHP Framework Documentation, Release 3.1.1

{{ form.messages("terms") }}

{{ form.render("Sign Up") }}

{{ form.render("csrf",["value": security.getToken()]) }} {{ form.messages("csrf") }}


{{ endForm() }}

Tutorial 7: Creating a Simple REST API

In this tutorial, we will explain how to create a simple application that provides a RESTful API using the different HTTP methods: • GET to retrieve and search data • POST to add data • PUT to update data • DELETE to delete data

Defining the API

The API consists of the following methods: Method URL Action GET /api/robots Retrieves all robots GET /api/robots/search/Astro Searches for robots with ‘Astro’ in their name GET /api/robots/2 Retrieves robots based on primary key POST /api/robots Adds a new robot PUT /api/robots/2 Updates robots based on primary key DELETE /api/robots/2 Deletes robots based on primary key

Creating the Application

As the application is so simple, we will not implement any full MVC environment to develop it. In this case, we will use a micro application to meet our goal. The following file structure is more than enough:

my-rest-api/ models/ Robots.php index.php .htaccess

First, we need an .htaccess file that contains all the rules to rewrite the URIs to the index.php file, that is our application:

RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f

92 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

RewriteRule ^((?s).*)$ index.php?_url=/$1 [QSA,L]

Then, in the index.php file we create the following:

$app= new Micro();

// Define the routes here

$app->handle();

Now we will create the routes as we defined above:

$app= new Micro();

// Retrieves all robots $app->get( "/api/robots", function () {

} );

// Searches for robots with $name in their name $app->get( "/api/robots/search/{name}", function ($name){

} );

// Retrieves robots based on primary key $app->get( "/api/robots/{id:[0-9]+}", function ($id){

} );

// Adds a new robot $app->post( "/api/robots", function () {

} );

// Updates robots based on primary key $app->put( "/api/robots/{id:[0-9]+}",

2.2. Tutorials 93 Phalcon PHP Framework Documentation, Release 3.1.1

function () {

} );

// Deletes robots based on primary key $app->delete( "/api/robots/{id:[0-9]+}", function () {

} );

$app->handle();

Each route is defined with a method with the same name as the HTTP method, as first parameter we pass a route pattern, followed by a handler. In this case, the handler is an anonymous function. The following route: '/api/ robots/{id:[0-9]+}', by example, explicitly sets that the “id” parameter must have a numeric format. When a defined route matches the requested URI then the application executes the corresponding handler.

Creating a Model

Our API provides information about ‘robots’, these data are stored in a database. The following model allows us to access that table in an object-oriented way. We have implemented some business rules using built-in validators and simple validations. Doing this will give us the peace of mind that saved data meet the requirements of our application:

validate( new InclusionIn( [ "field" =>"type", "domain" =>[ "droid", "mechanical", "virtual", ] ) ) );

// Robot name must be unique $this->validate( new Uniqueness(

94 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

[ "field" =>"name", "message" =>"The robot name must be unique", ] ) );

// Year cannot be less than zero if ($this->year<0){ $this->appendMessage( new Message("The year cannot be less than zero") ); }

// Check if any messages have been produced if ($this->validationHasFailed() === true){ return false; } } }

Now, we must set up a connection to be used by this model and load it within our app:

// Use Loader() to autoload our model $loader= new Loader();

$loader->registerNamespaces( [ "Store\\Toys" => __DIR__."/models/", ] );

$loader->register();

$di= new FactoryDefault();

// Set up the database service $di->set( "db", function () { return new PdoMysql( [ "host" =>"localhost", "username" =>"asimov", "password" =>"zeroth", "dbname" =>"robotics", ] ); } );

2.2. Tutorials 95 Phalcon PHP Framework Documentation, Release 3.1.1

// Create and bind the DI to the application $app= new Micro($di);

Retrieving Data

The first “handler” that we will implement is which by method GET returns all available robots. Let’s use PHQL to perform this simple query returning the results as JSON:

// Retrieves all robots $app->get( "/api/robots", function () use ($app){ $phql="SELECT * FROM Store\\Toys\\Robots ORDER BY name";

$robots= $app->modelsManager->executeQuery($phql);

$data= [];

foreach ($robots as $robot){ $data[]=[ "id" => $robot->id, "name" => $robot->name, ]; }

echo json_encode($data); } );

PHQL, allow us to write queries using a high-level, object-oriented SQL dialect that internally translates to the right SQL statements depending on the database system we are using. The clause “use” in the anonymous function allows us to pass some variables from the global to local scope easily. The searching by name handler would look like:

// Searches for robots with $name in their name $app->get( "/api/robots/search/{name}", function ($name) use ($app){ $phql="SELECT * FROM Store\\Toys\\Robots WHERE name LIKE :name: ORDER BY ˓→name";

$robots= $app->modelsManager->executeQuery( $phql, [ "name" =>"%". $name."%" ] );

$data= [];

foreach ($robots as $robot){ $data[]=[

96 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

"id" => $robot->id, "name" => $robot->name, ]; }

echo json_encode($data); } );

Searching by the field “id” it’s quite similar, in this case, we’re also notifying if the robot was found or not:

// Retrieves robots based on primary key $app->get( "/api/robots/{id:[0-9]+}", function ($id) use ($app){ $phql="SELECT * FROM Store\\Toys\\Robots WHERE id = :id:";

$robot= $app->modelsManager->executeQuery( $phql, [ "id" => $id, ] )->getFirst();

// Create a response $response= new Response();

if ($robot === false){ $response->setJsonContent( [ "status" =>"NOT-FOUND" ] ); } else { $response->setJsonContent( [ "status" =>"FOUND", "data" =>[ "id" => $robot->id, "name" => $robot->name ] ] ); }

return $response; } );

2.2. Tutorials 97 Phalcon PHP Framework Documentation, Release 3.1.1

Inserting Data

Taking the data as a JSON string inserted in the body of the request, we also use PHQL for insertion:

// Adds a new robot $app->post( "/api/robots", function () use ($app){ $robot= $app->request->getJsonRawBody();

$phql="INSERT INTO Store \\Toys\\Robots (name, type, year) VALUES (:name:,

˓→:type:, :year:)";

$status= $app->modelsManager->executeQuery( $phql, [ "name" => $robot->name, "type" => $robot->type, "year" => $robot->year, ] );

// Create a response $response= new Response();

// Check if the insertion was successful if ($status->success() === true){ // Change the HTTP status $response->setStatusCode(201,"Created");

$robot->id= $status->getModel()->id;

$response->setJsonContent( [ "status" =>"OK", "data" => $robot, ] ); } else { // Change the HTTP status $response->setStatusCode(409,"Conflict");

// Send errors to the client $errors= [];

foreach ($status->getMessages() as $message){ $errors[]= $message->getMessage(); }

$response->setJsonContent( [ "status" =>"ERROR", "messages" => $errors, ]

98 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

); }

return $response; } );

Updating Data

The data update is similar to insertion. The “id” passed as parameter indicates what robot must be updated:

// Updates robots based on primary key $app->put( "/api/robots/{id:[0-9]+}", function ($id) use ($app){ $robot= $app->request->getJsonRawBody();

$phql="UPDATE Store \\Toys\\Robots SET name = :name:, type = :type:, year =

˓→:year: WHERE id = :id:";

$status= $app->modelsManager->executeQuery( $phql, [ "id" => $id, "name" => $robot->name, "type" => $robot->type, "year" => $robot->year, ] );

// Create a response $response= new Response();

// Check if the insertion was successful if ($status->success() === true){ $response->setJsonContent( [ "status" =>"OK" ] ); } else { // Change the HTTP status $response->setStatusCode(409,"Conflict");

$errors= [];

foreach ($status->getMessages() as $message){ $errors[]= $message->getMessage(); }

$response->setJsonContent( [ "status" =>"ERROR",

2.2. Tutorials 99 Phalcon PHP Framework Documentation, Release 3.1.1

"messages" => $errors, ] ); }

return $response; } );

Deleting Data

The data delete is similar to update. The “id” passed as parameter indicates what robot must be deleted:

// Deletes robots based on primary key $app->delete( "/api/robots/{id:[0-9]+}", function ($id) use ($app){ $phql="DELETE FROM Store \\Toys\\Robots WHERE id = :id:";

$status= $app->modelsManager->executeQuery( $phql, [ "id" => $id, ] );

// Create a response $response= new Response();

if ($status->success() === true){ $response->setJsonContent( [ "status" =>"OK" ] ); } else { // Change the HTTP status $response->setStatusCode(409,"Conflict");

$errors= [];

foreach ($status->getMessages() as $message){ $errors[]= $message->getMessage(); }

$response->setJsonContent( [ "status" =>"ERROR", "messages" => $errors, ] ); }

100 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

return $response; } );

Testing our Application

Using curl we’ll test every route in our application verifying its proper operation. Obtain all the robots: curl -i -X GET http://localhost/my-rest-api/api/robots

HTTP/1.1 200OK Date: Tue, 21 Jul 2015 07:05:13 GMT Server: Apache/2.2.22(Unix) DAV/2 Content-Length: 117 Content-Type: text/html; charset=UTF-8

[{"id":"1","name":"Robotina"},{"id":"2","name":"Astro Boy"},{"id":"3","name":

˓→"Terminator"}]

Search a robot by its name: curl -i -X GET http://localhost/my-rest-api/api/robots/search/Astro

HTTP/1.1 200OK Date: Tue, 21 Jul 2015 07:09:23 GMT Server: Apache/2.2.22(Unix) DAV/2 Content-Length: 31 Content-Type: text/html; charset=UTF-8

[{"id":"2","name":"Astro Boy"}]

Obtain a robot by its id: curl -i -X GET http://localhost/my-rest-api/api/robots/3

HTTP/1.1 200OK Date: Tue, 21 Jul 2015 07:12:18 GMT Server: Apache/2.2.22(Unix) DAV/2 Content-Length: 56 Content-Type: text/html; charset=UTF-8

{"status":"FOUND","data":{"id":"3","name":"Terminator"}}

Insert a new robot: curl -i -X POST -d '{"name":"C-3PO","type":"droid","year":1977}' http://localhost/my-rest-api/api/robots

HTTP/1.1 201 Created Date: Tue, 21 Jul 2015 07:15:09 GMT Server: Apache/2.2.22(Unix) DAV/2 Content-Length: 75 Content-Type: text/html; charset=UTF-8

{"status":"OK","data":{"name":"C-3PO","type":"droid","year":1977,"id":"4"}}

2.2. Tutorials 101 Phalcon PHP Framework Documentation, Release 3.1.1

Try to insert a new robot with the name of an existing robot: curl -i -X POST -d '{"name":"C-3PO","type":"droid","year":1977}' http://localhost/my-rest-api/api/robots

HTTP/1.1 409 Conflict Date: Tue, 21 Jul 2015 07:18:28 GMT Server: Apache/2.2.22(Unix) DAV/2 Content-Length: 63 Content-Type: text/html; charset=UTF-8

{"status":"ERROR","messages":["The robot name must be unique"]}

Or update a robot with an unknown type: curl -i -X PUT -d '{"name":"ASIMO","type":"humanoid","year":2000}' http://localhost/my-rest-api/api/robots/4

HTTP/1.1 409 Conflict Date: Tue, 21 Jul 2015 08:48:01 GMT Server: Apache/2.2.22(Unix) DAV/2 Content-Length: 104 Content-Type: text/html; charset=UTF-8

{"status":"ERROR","messages":["Value of field 'type' must be part of list: droid, mechanical, virtual"]}

Finally, delete a robot: curl -i -X DELETE http://localhost/my-rest-api/api/robots/4

HTTP/1.1 200OK Date: Tue, 21 Jul 2015 08:49:29 GMT Server: Apache/2.2.22(Unix) DAV/2 Content-Length: 15 Content-Type: text/html; charset=UTF-8

{"status":"OK"}

Conclusion

As we have seen, develop a RESTful API with Phalcon is easy. Later in the documentation we’ll explain in detail how to use micro applications and the PHQL language.

List of examples

Following examples are full applications you can use to learn more about Phalcon and use them as base for your own websites/applications:

102 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Components

Injeksi Ketergantungan/Lokasi Service

Before reading this section, it is wise to read the section which explains why Phalcon uses service location and dependency injection. Phalcon\Di adalah komponen yang mengimplementasi Dependency Injection dan Location of services dan merupakan kontainer service. Karena Phalcon memiliki ketergantungan minimal (highly decoupled), Phalcon\Di penting untuk mengintegrasikan beragam komponen framework. Developer dapat juga menggunakan komponen ini untuk menginjeksi ketergantungan dan mengelola instance global kelas-kelas berbeda yang digunakan aplikasi. Pada dasarnya, komponen ini mengimplementasi pola Inversion of Control . Dengan menerapkannya, objek-objek tidak menerima ketergantungannya menggunakan setter atau konstruktor, namun meminta service dependency injec- tor. Ini menurunkan kompleksitias keseluruhan karena hanya ada satu cara untuk mendapatkan ketergantungan yang dibutuhkan dalam sebuah komponen. Tambahan lagi, pola ini menaikkan testabilitas dalam kode, sehingga menjadikannya tidak rawan kesalahan.

Mendaftarkan service dalam Kontainer

Framework sendiri atau developer dapat mendaftarkan service. Ketika sebuah komponen A membutuhkan komponen B (atau instance kelas itu) untuk bekerja, ia dapat meminta komponen B dari kontainer, daripada menciptakan instance komponen B baru. Cara kerja ini memberi kita banyak keuntungan: • Kita dapat mudah mengganti sebuah komponen dengan milik kita sendiri atau pihak ketiga. • Kita memiliki kendali penuh atas inisialisasi objek, memungkinkan kita mengatur objek ini sebelum mem- berikan ke komponen. • Kita dapat instance global komponen dengan cara yang terstruktur dan menyatu. Service dapat didaftarkan dengan beberapa jenis definisi:

Regitrasi Sederhana

Seperti terlihat sebelumnya, ada beberapa cara untuk mendaftarkan service. Ini kita sebut sederhana:

String

Jenis mengharapkan nama kelas valid, mengembalikan sebuah objek dari kelas yang ditentukan, jika kelas tidak dimuat ia akan diciptakan menggunakan auto-loader. Jenis definisi ini tidak mengizinkan untuk menentukan argumen untuk kontruktor kelas atau parameter:

// mengembalikan new Phalcon\Http\Request(); $di->set( "request", "Phalcon\\Http\\Request" );

2.3. Components 103 Phalcon PHP Framework Documentation, Release 3.1.1

Objek

Jenis ini mengharapkan sebuah objek. Karena fakta bahwa objek tidak perlu di resolve karena ia sudah objek, bisa dibilang ini tidak benar-benar dependency injection, namun ia berguna jika anda ingin memaksa ketergantungan yang diberikan selalu objek atau nilai yang sama:

// mengembalikan new Phalcon\Http\Request(); $di->set( "request", new Request() );

Closure/Fungsi Anonymous

Metode ini menawarkan kebebasan lebih besar dengan membangun ketergantungan sesuai keinginan, namun, ia sulit mengubah beberapa parameter secara ekternal tanpa mengubah definisi ketergantungan:

$di->set( "db", function () { return new PdoMysql( [ "host" =>"localhost", "username" =>"root", "password" =>"secret", "dbname" =>"blog", ] ); } );

Beberapa keterbatasan dapat diatasi dengan melewatkan variabel tambahan ke lingkungan closure:

$config= new Config( [ "host" =>"127.0.0.1", "username" =>"user", "password" =>"pass", "dbname" =>"my_database", ] );

// Menggunakan variabel $config dalam scope saat ini

104 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

$di->set( "db", function () use ($config){ return new PdoMysql( [ "host" => $config->host, "username" => $config->username, "password" => $config->password, "dbname" => $config->name, ] ); } );

You can also access other DI services using the get() method:

$di->set( "config", function () { return new Config( [ "host" =>"127.0.0.1", "username" =>"user", "password" =>"pass", "dbname" =>"my_database", ] ); } );

// Using the 'config' service from the DI $di->set( "db", function () { $config= $this->get("config");

return new PdoMysql( [ "host" => $config->host, "username" => $config->username, "password" => $config->password, "dbname" => $config->name, ] ); } );

Registrasi Kompleks

Jika diperlukan untuk mengubah definisi service tanpa perlu menciptakan/resolve service, maka, kita butuh menen- tukan service menggunakan sintaks array. Menentukan service menggunakan definisi array dapat terlihat lebih ramai:

2.3. Components 105 Phalcon PHP Framework Documentation, Release 3.1.1

// Daftarkan service 'logger' dengan nama kelas dan parameter $di->set( "logger", [ "className" =>"Phalcon \\Logger\\Adapter\\File", "arguments" =>[ [ "type" =>"parameter", "value" =>"../apps/logs/error.log", ] ] ] );

// Menggunakan fungsi anonim $di->set( "logger", function () { return new LoggerFile("../apps/logs/error.log"); } );

Kedua registrasi service diatas menghasilkan hasil sama. Namun definisi array, memungkinkan pengubahan parameter service bila diperlukan:

// Ubah nama kelas service $di->getService("logger")->setClassName("MyCustomLogger");

// Ubah parameter pertama tanpa menciptakan logger $di->getService("logger")->setParameter( 0, [ "type" =>"parameter", "value" =>"../apps/logs/error.log", ] );

Tambahan lagi menggunakan sintaks array anda dapat menggunakan tiga jenis dependency injection:

Injeksi Konstructor

Injeksi jenis ini melewatkan ketergantungan/argumen ke konstruktor kelas. Anggap kita memiliki komponen berikut:

106 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

{ /** * @var Response */ protected $_response;

protected $_someFlag;

public function __construct(Response $response, $someFlag) { $this->_response= $response; $this->_someFlag= $someFlag; } }

Service ini dapat didaftarkan dengan cara berikut:

$di->set( "response", [ "className" =>"Phalcon \\Http\\Response" ] );

$di->set( "someComponent", [ "className" =>"SomeApp \\SomeComponent", "arguments" =>[ [ "type" =>"service", "name" =>"response", ], [ "type" =>"parameter", "value" => true, ], ] ] );

Service “response” (Phalcon\Http\Response) di resolve lalu dilewatkan ke argumen pertama konstruktor, sedangkan yang kedua adalah nilai boolean (true) yang dilewatkan apa adanya.

Injeksi Setter

Kelas mungkin punya setter untuk menyisipkan ketergantungan tidak wajib, kelas kita sebelumnya dapat diubah untuk menerima ketergantungan dengan setter:

namespace SomeApp;

2.3. Components 107 Phalcon PHP Framework Documentation, Release 3.1.1

use Phalcon\Http\Response; class SomeComponent { /** * @var Response */ protected $_response;

protected $_someFlag;

public function setResponse(Response $response) { $this->_response= $response; }

public function setFlag($someFlag) { $this->_someFlag= $someFlag; } }

Service dengan injeksi setter dapat didaftarkan seperti berikut:

$di->set( "response", [ "className" =>"Phalcon \\Http\\Response", ] );

$di->set( "someComponent", [ "className" =>"SomeApp \\SomeComponent", "calls" =>[ [ "method" =>"setResponse", "arguments" =>[ [ "type" =>"service", "name" =>"response", ] ] ], [ "method" =>"setFlag", "arguments" =>[ [ "type" =>"parameter", "value" => true, ] ]

108 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

] ] ] );

Injeksi Properti

Strategi kurang umum adalah menyisipkan ketergantungan atau parameter langsung melalui atribut publik kelas:

public $someFlag; }

Service dengan injeksi properti dapat didaftarkan sebagai berikut:

$di->set( "response", [ "className" =>"Phalcon \\Http\\Response", ] );

$di->set( "someComponent", [ "className" =>"SomeApp \\SomeComponent", "properties" =>[ [ "name" =>"response", "value" =>[ "type" =>"service", "name" =>"response", ], ], [ "name" =>"someFlag", "value" =>[ "type" =>"parameter", "value" => true, ], ] ]

2.3. Components 109 Phalcon PHP Framework Documentation, Release 3.1.1

] );

Jenis parameter yang didukung termasuk berikut ini: Jenis Keterangan Contoh pa- Mewakili nilai asli yang ["type" => "parameter", "value" => 1234] rame- dilewatkan sebagai parameter ter ser- Mewakili service lain dalam ["type" => "service", "name" => "request"] vice kontainer service in- Mewakili objek yang harus ["type" => "instance", "className" => stance diciptakan dinamis "DateTime", "arguments" => ["now"]] Resolve service yang definisinya kompleks mungkin lebih lambat dibandingkan yang sederhana seperti yang sudah terlihat sebelumnya. Namun, ia menyediakan pendekatan yang lebih kokoh untuk mendefinisi dan menginjeksi ser- vice. Mencampur jenis definisi berbeda diizinkan, semua orang dapat memutuskan cara apa yang paling cocok mendaftarkan service tergantung kebutuhan aplikasi.

Array Syntax

Sintaks array juga diizinkan untuk mendaftarkan service:

use Phalcon\Di; use Phalcon\Http\Request;

// Buat Dependency Injector Container $di= new Di();

// Menggunakan nama kelas $di["request"]="Phalcon \\Http\\Request";

// Menggunakan fungsi anonymous function, instance akan dimuat secara lazy load $di["request"]= function () { return new Request(); };

// Mendaftarkan instance langsung $di["request"]= new Request();

// Menggunakan definisi array $di["request"]=[ "className" =>"Phalcon \\Http\\Request", ];

Dicontoh diatas, ketika framework butuh mengakses data request, ia akan meminta service yang diidentifikasi sebagai ‘request’ dalam kontainer. Kontainer kemudian mengembalikan instance service yang diminta. Developer mungkin suatu saat mengganti sebuah komponen ketika mereka butuh. Tiap metode (ditunjukkan di contoh diatas) yang digunakan untuk mengatur/mendaftarkan service punya kelebihan dan kekurangan. Tergantung developer dan kebutuhan tertentu yang mengarahkan mana yang digunakan.

110 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Mengatur service dengan string mudah, namun kurang fleksibilitas. Mengatur service dengan array menawarkan lebih banyak fleksibilitas, namun menjadikan kode lebih rumit. Fungsi lambda adalah keseimbangan bagus diantara keduanya, namun dapat menyebabkan lebih banyak maintenance dari yang diharapkan. Phalcon\Di menawarkan lazy loading untuk semua service yang disimpan. Kecuali developer memilih menciptakan objek langsung dan menyimpannya dalam kontainer, tiap objek yang disimpan didalamnya (melalui array, string dan lain-lain) akan di muat secara lazy load yakni hanya akan diciptakan ketika diminta.

Resolving Services

Mendapatkan service dari kontainer hanya masalah memanggil metode “get”. Instance baru service akan dikemba- likan:

get("request");

Atau menggunakan metode magic:

$request= $di->getRequest();

Atau menggunakan sintaks akses array:

$request= $di["request"];

Argumen dapat dilewatkan ke konstruktor dengan menambahkan parameter array ke metode “get”:

// new MyComponent("some-parameter", "other") $component= $di->get( "MyComponent", [ "some-parameter", "other", ] );

Event

Phalcon\Di mampu mengirim event ke EventsManager jika ada. Event dipicu menggunakan tipe “di”. Beberapa event ketika mengembalikan nilai boolean false dapat menghentikan operasi aktif. Event berikut didukung: Nama Event Dipicu Bisa stop Dipicu operasi? di beforeSer- Dipicu sebelum resolve service. Listener menerima nama service dan Tidak Lis- viceResolve parameter yang dilewatkan. teners afterSer- Dipicu sebelum resolve service. Listener menerima nama service, Tidak Lis- viceResolve instance dan parameter yang dilewatkan. teners

2.3. Components 111 Phalcon PHP Framework Documentation, Release 3.1.1

Service Berbagi

Service dapat didaftarkan sebagai “shared” services yang berarti bahwa mereka selalu bertindak sebagai singletons. Service diresolve untuk pertama kali, instance sama dikembalikan tiap kali konsumer meminta service dari kontainer:

// daftarkan service session sebagai "always shared" $di->setShared( "session", function () { $session= new SessionFiles();

$session->start();

return $session; } );

// Temukan service untuk pertama kali $session= $di->get("session");

// Mengembalikan objek yang sudah diciptakan pertama kali $session= $di->getSession();

Cara lain mendaftarkan shared service adalah melewatkan “true” sebagai parameter ketiga “set”:

// Daftarkan service session sebagai "always shared" $di->set( "session", function () { // ... }, true );

Ketika sebuah service tidak didaftarkan sebagai service berbagi dan anda ingin memastikan instance yang sama diakses tiap kali service diambil dari DI, anda dapat menggunakan metode ‘getShared’:

$request= $di->getShared("request");

Memanipulasi masing-masing Service

Setelah service didaftarkan dalam kontainer service, anda dapat mengambilnya untuk dimanipulasi secara terpisah:

// Daftarkan service "request" $di->set("request","Phalcon \\Http\\Request");

112 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

// Ambil service $requestService= $di->getService("request");

// Ubah definisi $requestService->setDefinition( function () { return new Request(); } );

// Ubah menjadi berbagi $requestService->setShared(true);

// Resolve service (mengembalikan instance Phalcon\Http\Request) $request= $requestService->resolve();

Menciptakan kelas melalui Service Container

Ketika anda meminta service ke kontainer service, jika ia tidak dapat menemukan service dengan nama sama ia akan mencoba memuat kelas dengan nama sama. Dengan perilaku ini kita dapat mengganti sembarang kelas dengan lainnya cuma dengan mendaftarkan service dengan nama itu:

// Daftarkan kontroler sebagai service $di->set( "IndexController", function () { $component= new Component();

return $component; }, true );

// Daftarkan kontroler sebagai service $di->set( "MyOtherComponent", function () { // Kembalikan komponen lain $component= new AnotherComponent();

return $component; } );

// Buat instance melalui service container $myComponent= $di->get("MyOtherComponent");

Anda dapat memanfaatkan ini, dengan selalu menciptakan kelas anda melalui service container (bahkan jika mereka tidak didaftarkan sebagai service). DI akan fallback ke autoloader yang valid yang akhirnya memuat kelas tersebut. Dengan melakukan ini, anda dapat mengganti sembarang kelas dimasa datang dengan mengimplementasi definisinya.

2.3. Components 113 Phalcon PHP Framework Documentation, Release 3.1.1

Menginjeksi DI secara otomatis

Jika sebuah kelas atau komponen memerlukan DI sendiri untuk menemukan service, DI dapat diinjeksi otomatis kedalam instance yang diciptakan, untuk melakukan ini, anda butuh mengimplementasi Phal- con\Di\InjectionAwareInterface dalam kelas anda:

public function setDi(DiInterface $di) { $this->_di= $di; }

public function getDi() { return $this->_di; } } lalu setelah service diresolve, $di akan dilewatkan ke setDi() otomatis:

// Daftarkan service $di->set("myClass","MyClass");

// Resolve service (Catatan: $myClass->setDi($di) dipanggil otomatis) $myClass= $di->get("myClass");

Mengelola service dalam file

Anda dapat mengelola lebih baik aplikasi anda dengan memindahkan pendaftaran service ke file terpisah daripada melakukan semua dalam bootstrap aplikasi:

$di->set( "router", function () { return include "../app/config/routes.php"; } );

Lalu dalam file (”../app/config/routes.php”) kembalikan objek yang diresolve:

114 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

$router= new MyRouter();

$router->post("/login"); return $router;

Mengakses DI cara statik

Jika diperlukan anda dapat mengakses DI yang diciptakan terakhir dalam fungsi statik dengan cara berikut:

getSession(); } }

Factory Default DI

Meski katakter Phalcon yang terpisah (decoupled) menawarkan kita kebebasan dan fleksibilitas bagus, mungkin kita cuma ingin menggunakannya sebagai framework full-stack. Untuk mencapai ini, framework menyediakan varian Phalcon\Di yang disebut Phalcon\Di\FactoryDefault. Kelas ini otomatis mendaftarkan service yang cocok yang diga- bung dengan framework untuk menjadikannya framework lengkap (full-stack).

$di= new FactoryDefault();

Konvensi Nama Service

Meski anda dapat mendaftarkan service dengan nama yang anda mau, Phalcon punya beberapa konvensi penamaan yang memungkinkan ia mendapatkan service bawaan dengan benar ketika anda membutuhkannya.

2.3. Components 115 Phalcon PHP Framework Documentation, Release 3.1.1

Nama Service Keterangan Default Shared dispatcher Controllers Dispatching Service Phalcon\Mvc\Dispatcher Ya router Routing Service Phalcon\Mvc\Router Ya url URL Generator Service Phalcon\Mvc\Url Ya request HTTP Request Environment Service Phalcon\Http\Request Ya response HTTP Response Environment Service Phalcon\Http\Response Ya cookies HTTP Cookies Management Service Phalcon\Http\Response\Cookies Ya filter Input Filtering Service Phalcon\Filter Ya flash Flash Messaging Service Phalcon\Flash\Direct Ya flashSession Flash Session Messaging Service Phalcon\Flash\Session Ya session Session Service Phalcon\Session\Adapter\Files Ya eventsManager Events Management Service Phalcon\Events\Manager Ya db Low-Level Database Connection Phalcon\Db Ya Service security Security helpers Phalcon\Security Ya crypt Encrypt/Decrypt data Phalcon\Crypt Ya tag HTML generation helpers Phalcon\Tag Ya escaper Contextual Escaping Phalcon\Escaper Ya annotations Annotations Parser Phalcon\Annotations\Adapter\Memory Ya modelsManager Models Management Service Phalcon\Mvc\Model\Manager Ya modelsMetadata Models Meta-Data Service Phal- Ya con\Mvc\Model\MetaData\Memory transactionMan- Models Transaction Manager Service Phal- Ya ager con\Mvc\Model\Transaction\Manager modelsCache Cache backend for models cache None Tidak viewsCache Cache backend for views fragments None Tidak

Mengimplementasi DI anda sendiri

Interface Phalcon\DiInterface harus diimplementasi untuk menciptakan DI anda sendiri menggantikan yang sudah disediakan oleh Phalcon atau melengkapi yang sudah ada.

The MVC Architecture

Phalcon offers the object-oriented classes, necessary to implement the Model, View, Controller architecture (often referred to as MVC) in your application. This design pattern is widely used by other web frameworks and desktop applications. MVC benefits include: • Isolation of business logic from the user interface and the database layer • Making it clear where different types of code belong for easier maintenance If you decide to use MVC, every request to your application resources will be managed by the MVC architecture. Phalcon classes are written in C language, offering a high performance approach of this pattern in a PHP based application.

Models

A model represents the information (data) of the application and the rules to manipulate that data. Models are primarily used for managing the rules of interaction with a corresponding database table. In most cases, each table in your

116 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1 database will correspond to one model in your application. The bulk of your application’s business logic will be concentrated in the models. Learn more

Views

Views represent the user interface of your application. Views are often HTML files with embedded PHP code that perform tasks related solely to the presentation of the data. Views handle the job of providing data to the web browser or other tool that is used to make requests from your application. Learn more

Controllers

The controllers provide the “flow” between models and views. Controllers are responsible for processing the incoming requests from the web browser, interrogating the models for data, and passing that data on to the views for presentation. Learn more

Menggunakan Kontroler

Aksi adalah metode pada sebuah kontroler yang menangani request. Defaultnya semua metode publik pada sebuah kontroler dipetakan ke aksi dan dapat diakses menggunakan sebuah URL. Aksi bertanggung jawab menerjemahkan request dan menciptakan respon. Respon biasanya dalam bentuk view yang dirender, namun ada juga cara lain untuk menciptakan respon. Contoh, ketika anda mengakses sebuah URL seperti berikut: http://localhost/blog/posts/show/2015/the-post-title Phal- con secara bawaan akan memecah tiap bagian seperti berikut: Phalcon Directory blog Controller posts Action show Parameter 2015 Parameter the-post-title Di kasus ini, PostsController akan menangani request ini. Tidak ada lokasi khusus untuk meletakkan kontroler dalam aplikasi, ia dapat dimuat menggunakan autoloaders, sehingga anda bebas mengorganisasi kontroler sesuai kebutuhan. Kontroler harus memiliki akhiran “Controller” sementara aksi menggunakan akhiran “Action”. Contoh sebuah kon- troler adalah sebagai berikut:

}

public function showAction($year, $postTitle) {

} }

2.3. Components 117 Phalcon PHP Framework Documentation, Release 3.1.1

Parameter URI tambahan didefinisi sebagai parameter aksi, sehingga mereka dapat diakses dengan mudah menggu- nakan variabel lokal. Sebuah kontroler bisa jadi diturunkan dari Phalcon\Mvc\Controller. Dengan melakukan ini, kontroler dapat memiliki akses mudah ke layanan aplikasi. Parameter tanpa nilai default ditangani seperlunya. Pengaturan nilai opsional untuk parameter dilakukan seperti biasa dalam PHP:

use Phalcon\Mvc\Controller;

class PostsController extends Controller { public function indexAction() {

}

public function showAction($year= 2015, $postTitle="some default title") {

} }

Parameter disalin dengan urutan sama ketika dilewatkan dalam sebuah route. Anda dapat memeroleh sembarang parameter dari namanya dengan cara berikut:

use Phalcon\Mvc\Controller;

class PostsController extends Controller { public function indexAction() {

}

public function showAction() { $year= $this->dispatcher->getParam("year"); $postTitle= $this->dispatcher->getParam("postTitle"); } }

Dispatch Loop

Dispatch loop akan dijalankan dalam Dispatcher sampai tidak ada aksi tersisa untuk dijalankan. Di contoh sebelumnya hanya satu aksi yang dijalankan. Kita akan melihat bagaimana forward() dapat menyediakan alir operasi yang lebih kompleks dalam dispatch loop, dengan mengarahkan eksekusi ke kontroler/aksi berbeda.

use Phalcon\Mvc\Controller;

class PostsController extends Controller {

118 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

public function indexAction() {

}

public function showAction($year, $postTitle) { $this->flash->error( "You don't have permission to access this area" );

// Arahkan alir ke aksi lain $this->dispatcher->forward( [ "controller" =>"users", "action" =>"signin", ] ); } }

Jika pengguna tidak memiliki izin untuk mengakses aksi tertentu maka mereka akan diarahkan ke kontroler Users dan aksi bernama signin.

}

public function signinAction() {

} }

Tidak ada batasan jumlah “forward” yang dapat anda miliki dalam aplikasi, selama mereka tidak menyebabkan ref- erensi sirkular, di mana dititik ini aplikasi akan dihentikan. Jika tidak ada aksi lain yang harus dikirim oleh dispatch loop, dispatcher otomatis memanggil lapisan view dalam MVC yang dikelola oleh Phalcon\Mvc\View.

Inisialiasi Kontroler

Phalcon\Mvc\Controller menawarkan metode initialize(), yang dijalankan pertama kali, sebelum semua aksi dieksekusi pada sebuah kontroler. Penggunaan metode __construct() tidak disarankan.

2.3. Components 119 Phalcon PHP Framework Documentation, Release 3.1.1

public $settings;

public function initialize() { $this->settings=[ "mySetting" =>"value", ]; }

public function saveAction() { if ($this->settings["mySetting"] ==="value"){ // ... } } }

Metode initialize() hanya dipanggil jika event ‘beforeExecuteRoute’ dieksekusi dengan sukses. Ini mencegah kode aplikasi dalam initializer tidak dapat dieksekusi tanpa otorisasi. Jika anda ingin menjalankan kode inisialiasi tepat setelah menciptakan objek kontroler anda dapat mengimplementasi metode onConstruct():

Ketahui bahwa metode onConstruct() dijalankan bahkan bila aksi yang harus dijalankan tidak ada dalam kontroler atau user tidak punya akses ke sana (berdasarkan kontrol akses kustom yang disediakan oleh developer).

Menginjeksi Services

Jika sebuah kontroler diturunkan dari Phalcon\Mvc\Controller maka mudah untuk mengakses service container dalam aplikasi. Contoh, jika kita mendaftarkan sebuah service seperti ini:

$di= new Di();

$di->set( "storage", function () { return new Storage( "/some/directory" ); },

120 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

true );

Anda dapat mengakses service tersebut dengan beberapa cara:

storage->save("/some/file");

// Mengakses service dari DI $this->di->get("storage")->save("/some/file");

// Cara lain mengakses service dengan magic getter $this->di->getStorage()->save("/some/file");

// Cara lain mengakses service dengan magic getter $this->getDi()->getStorage()->save("/some/file");

// Menggunkana sintaks array $this->di["storage"]->save("/some/file"); } }

Jika anda menggunakan Phalcon sebagai sebuah full-stack framework, anda dapat membaca service bawaan yang disediakan dalam framework.

Request dan Response

Diasumsikan framework menyediakan sejumlah service yang telah terdaftar. Kita menjelaskan bagaimana berinter- aksi dengan lingkungan HTTP. Service “request” mengandung instance Phalcon\Http\Request dan “response” berisi Phalcon\Http\Response mewakili apa yang akan dikirim kembali ke klien.

}

public function saveAction() { // Uji apakah request dibuat dengan POST if ($this->request->isPost()) { // Akses data POST $customerName= $this->request->getPost("name"); $customerBorn= $this->request->getPost("born");

2.3. Components 121 Phalcon PHP Framework Documentation, Release 3.1.1

} } }

Objek response biasanya tidak digunakan secara langsung, ia dibuat sebelum eksekusi aksi, kadang kala - seperti dalam event afterDispatch - cukup berguna bila response dapat diakses langsung:

}

public function notFoundAction() { // Kirim response header HTTP 404 $this->response->setStatusCode(404,"Not Found"); } }

Pelajari lebih lanjut tentang lingkungan HTTP di artikel request dan response.

Data Session

Session membantu kita mengelola data persisten antar request. Anda dapat mengakses Phalcon\Session\Bag dari sembarang kontroler untuk membungkus data yang harus dibuat persisten.

persistent->name="Michael"; }

public function welcomeAction() { echo "Welcome,", $this->persistent->name; } }

Menggunakan Service sebagai Kontroler

Service dapat bertindak sebagai kontroler, kelas kontroler selalu diminta dari service container. Dengan demikian, tiap kelas lain yang terdaftar dengan nama sama dapat dengan mudah mengganti sebuah kontroler:

122 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

// Daftarkan kontroler sebagai service $di->set( "IndexController", function () { $component= new Component();

return $component; } );

// Daftarkan kontroler dengan namespace sebagai service $di->set( "Backend\\Controllers\\IndexController", function () { $component= new Component();

return $component; } );

Event dalam Kontroler

Kontroler otomatis bertindak sebagai listener untuk dispatcher event, mengimplementasi metode dengan nama tersebut memungkinkan anda untuk mengimplementasi hook point sebelum/sesudah aksi dieksekusi:

getActionName() ==="save"){ $this->flash->error( "You don't have permission to save posts" );

$this->dispatcher->forward( [ "controller" =>"home", "action" =>"index", ] );

return false; } }

public function afterExecuteRoute($dispatcher) { // Dieksekusi tiap kali setelah aksi yang ditemukan }

2.3. Components 123 Phalcon PHP Framework Documentation, Release 3.1.1

}

Working with Models

A model represents the information (data) of the application and the rules to manipulate that data. Models are primarily used for managing the rules of interaction with a corresponding database table. In most cases, each table in your database will correspond to one model in your application. The bulk of your application’s business logic will be concentrated in the models. Phalcon\Mvc\Model is the base for all models in a Phalcon application. It provides database independence, basic CRUD functionality, advanced finding capabilities, and the ability to relate models to one another, among other ser- vices. Phalcon\Mvc\Model avoids the need of having to use SQL statements because it translates methods dynamically to the respective database engine operations. Models are intended to work with the database on a high layer of abstraction. If you need to work with databases at a lower level check out the Phalcon\Db component documentation.

Creating Models

A model is a class that extends from Phalcon\Mvc\Model. Its class name should be in camel case notation:

}

If you’re using PHP 5.4/5.5 it is recommended you declare each column that makes part of the model in order to save memory and reduce the memory allocation. By default, the model “Store\Toys\RobotParts” will refer to the table “robot_parts”. If you want to manually specify another name for the mapping table, you can use the setSource() method:

setSource("toys_robot_parts"); } }

The model RobotParts now maps to “toys_robot_parts” table. The initialize() method aids in setting up the model with a custom behavior i.e. a different table.

124 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

The initialize() method is only called once during the request, it’s intended to perform initializations that apply for all instances of the model created within the application. If you want to perform initialization tasks for every instance created you can use the onConstruct() method:

Public properties vs. Setters/Getters

Models can be implemented with properties of public scope, meaning that each property can be read/updated from any part of the code that has instantiated that model class without any restrictions:

public $name;

public $price; }

By using getters and setters you can control which properties are visible publicly perform various transformations to the data (which would be impossible otherwise) and also add validation rules to the data stored in the object:

protected $name;

protected $price;

2.3. Components 125 Phalcon PHP Framework Documentation, Release 3.1.1

public function getId() { return $this->id; }

public function setName($name) { // The name is too short? if (strlen($name)< 10){ throw new InvalidArgumentException( "The name is too short" ); }

$this->name= $name; }

public function getName() { return $this->name; }

public function setPrice($price) { // Negative prices aren't allowed if ($price<0){ throw new InvalidArgumentException( "Price can't be negative" ); }

$this->price= $price; }

public function getPrice() { // Convert the value to double before be used return (double) $this->price; } }

Public properties provide less complexity in development. However getters/setters can heavily increase the testability, extensibility and maintainability of applications. Developers can decide which strategy is more appropriate for the application they are creating. The ORM is compatible with both schemes of defining properties. Underscores in property names can be problematic when using getters and setters. If you use underscores in your property names, you must still use camel case in your getter/setter declarations for use with magic methods. (e.g. $model->getPropertyName instead of $model->getProperty_name, $model- >findByPropertyName instead of $model->findByProperty_name, etc.). As much of the system expects camel case, and underscores are commonly removed, it is recommended to name your properties in the manner shown throughout the documentation. You can use a column map (as described above) to ensure proper mapping of your properties to their database counterparts.

126 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Understanding Records To Objects

Every instance of a model represents a row in the table. You can easily access record data by reading object properties. For example, for a table “robots” with the records:

mysql> select * from robots; +----+------+------+------+ | id | name | type | year | +----+------+------+------+ |1 | Robotina | mechanical | 1972| |2 | Astro Boy | mechanical | 1952| |3 | Terminator | cyborg | 2029| +----+------+------+------+ 3 rows in set(0.00 sec)

You could find a certain record by its primary key and then print its name:

use Store\Toys\Robots;

// Find record with id = 3 $robot= Robots::findFirst(3);

// Prints "Terminator" echo $robot->name;

Once the record is in memory, you can make modifications to its data and then save changes:

use Store\Toys\Robots;

$robot= Robots::findFirst(3);

$robot->name="RoboCop";

$robot->save();

As you can see, there is no need to use raw SQL statements. Phalcon\Mvc\Model provides high database abstraction for web applications.

Finding Records

Phalcon\Mvc\Model also offers several methods for querying records. The following examples will show you how to query one or more records from a model:

use Store\Toys\Robots;

// How many robots are there? $robots= Robots::find(); echo "There are", count($robots)," \n";

// How many mechanical robots are there? $robots= Robots::find("type = 'mechanical'");

2.3. Components 127 Phalcon PHP Framework Documentation, Release 3.1.1

echo "There are", count($robots)," \n";

// Get and print virtual robots ordered by name $robots= Robots::find( [ "type = 'virtual'", "order" =>"name", ] ); foreach ($robots as $robot){ echo $robot->name," \n"; }

// Get first 100 virtual robots ordered by name $robots= Robots::find( [ "type = 'virtual'", "order" =>"name", "limit" => 100, ] ); foreach ($robots as $robot){ echo $robot->name," \n"; }

If you want find record by external data (such as user input) or variable data you must use Binding Parameters. You could also use the findFirst() method to get only the first record matching the given criteria:

// What's the first robot in robots table? $robot= Robots::findFirst(); echo "The robot name is", $robot->name," \n";

// What's the first mechanical robot in robots table? $robot= Robots::findFirst("type = 'mechanical'"); echo "The first mechanical robot name is", $robot->name," \n";

// Get first virtual robot ordered by name $robot= Robots::findFirst( [ "type = 'virtual'", "order" =>"name", ] ); echo "The first virtual robot name is", $robot->name," \n";

Both find() and findFirst() methods accept an associative array specifying the search criteria:

$robot= Robots::findFirst(

128 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

[ "type = 'virtual'", "order" =>"name DESC", "limit" => 30, ] );

$robots= Robots::find( [ "conditions" =>"type = ?1", "bind" =>[ 1 =>"virtual", ] ] );

The available query options are: Pa- Description Example ram- eter con- Search conditions for the find operation. Is used to extract only "conditions" => "name di- those records that fulfill a specified criterion. By default LIKE 'steve%'" tions Phalcon\Mvc\Model assumes the first parameter are the conditions. columnsReturn specific columns instead of the full columns in the model. "columns" => "id, name" When using this option an incomplete object is returned bind Bind is used together with options, by replacing placeholders and "bind" => ["status" => escaping values thus increasing security "A", "type" => "some-time"] bind- When binding parameters, you can use this parameter to define "bindTypes" => Types additional casting to the bound parameters increasing even more [Column::BIND_PARAM_STR, the security Column::BIND_PARAM_INT] or- Is used to sort the resultset. Use one or more fields separated by "order" => "name DESC, der commas. status" limit Limit the results of the query to results to certain range "limit" => 10 off- Offset the results of the query by a certain amount "offset" => 5 set group Allows to collect data across multiple records and group the "group" => "name, results by one or more columns status" for_updateWith this option, Phalcon\Mvc\Model reads the latest available "for_update" => true data, setting exclusive locks on each row it reads shared_lockWith this option, Phalcon\Mvc\Model reads the latest available "shared_lock" => true data, setting shared locks on each row it reads cache Cache the resultset, reducing the continuous access to the "cache" => ["lifetime" relational system => 3600, "key" => "my-find-key"] hy- Sets the hydration strategy to represent each returned record in the "hydration" => dra- result Resultset::HYDRATE_OBJECTS tion If you prefer, there is also available a way to create queries in an object-oriented way, instead of using an array of parameters:

2.3. Components 129 Phalcon PHP Framework Documentation, Release 3.1.1

use Store\Toys\Robots;

$robots= Robots::query() ->where("type = :type:") ->andWhere("year < 2000") ->bind(["type" =>"mechanical"]) ->order("name") ->execute();

The static method query() returns a Phalcon\Mvc\Model\Criteria object that is friendly with IDE autocompleters. All the queries are internally handled as PHQL queries. PHQL is a high-level, object-oriented and SQL-like lan- guage. This language provide you more features to perform queries like joining other models, define groupings, add aggregations etc. Lastly, there is the findFirstBy() method. This method expands on the findFirst() method mentioned earlier. It allows you to quickly perform a retrieval from a table by using the property name in the method itself and passing it a parameter that contains the data you want to search for in that column. An example is in order, so taking our Robots model mentioned earlier:

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model { public $id;

public $name;

public $price; }

We have three properties to work with here: $id, $name and $price. So, let’s say you want to retrieve the first record in the table with the name ‘Terminator’. This could be written like:

use Store\Toys\Robots;

$name="Terminator";

$robot= Robots::findFirstByName($name);

if ($robot){ echo "The first robot with the name". $name." cost". $robot->price."."; } else { echo "There were no robots found in our table with the name". $name."."; }

Notice that we used ‘Name’ in the method call and passed the variable $name to it, which contains the name we are looking for in our table. Notice also that when we find a match with our query, all the other properties are available to us as well.

130 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Model Resultsets

While findFirst() returns directly an instance of the called class (when there is data to be returned), the find() method returns a Phalcon\Mvc\Model\Resultset\Simple. This is an object that encapsulates all the functionality a resultset has like traversing, seeking specific records, counting, etc. These objects are more powerful than standard arrays. One of the greatest features of the Phalcon\Mvc\Model\Resultset is that at any time there is only one record in memory. This greatly helps in memory management especially when working with large amounts of data.

// Get all robots $robots= Robots::find();

// Traversing with a foreach foreach ($robots as $robot){ echo $robot->name," \n"; }

// Traversing with a while $robots->rewind(); while ($robots->valid()) { $robot= $robots->current();

echo $robot->name," \n";

$robots->next(); }

// Count the resultset echo count($robots);

// Alternative way to count the resultset echo $robots->count();

// Move the internal cursor to the third robot $robots->seek(2);

$robot= $robots->current();

// Access a robot by its position in the resultset $robot= $robots[5];

// Check if there is a record in certain position if (isset($robots[3])) { $robot= $robots[3]; }

// Get the first record in the resultset $robot= $robots->getFirst();

// Get the last record $robot= $robots->getLast();

2.3. Components 131 Phalcon PHP Framework Documentation, Release 3.1.1

Phalcon’s resultsets emulate scrollable cursors, you can get any row just by accessing its position, or seeking the internal pointer to a specific position. Note that some database systems don’t support scrollable cursors, this forces to re-execute the query in order to rewind the cursor to the beginning and obtain the record at the requested position. Similarly, if a resultset is traversed several times, the query must be executed the same number of times. As storing large query results in memory could consume many resources, resultsets are obtained from the database in chunks of 32 rows - reducing the need to re-execute the request in several cases. Note that resultsets can be serialized and stored in a cache backend. Phalcon\Cache can help with that task. However, serializing data causes Phalcon\Mvc\Model to retrieve all the data from the database in an array, thus consuming more memory while this process takes place.

// Query all records from model parts $parts= Parts::find();

// Store the resultset into a file file_put_contents( "cache.txt", serialize($parts) );

// Get parts from file $parts= unserialize( file_get_contents("cache.txt") );

// Traverse the parts foreach ($parts as $part){ echo $part->id; }

Filtering Resultsets

The most efficient way to filter data is setting some search criteria, databases will use indexes set on tables to return data faster. Phalcon additionally allows you to filter the data using PHP using any resource that is not available in the database:

$customers= Customers::find();

$customers= $customers->filter( function ($customer){ // Return only customers with a valid e-mail if (filter_var($customer->email, FILTER_VALIDATE_EMAIL)) { return $customer; } } );

132 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Binding Parameters

Bound parameters are also supported in Phalcon\Mvc\Model. You are encouraged to use this methodology so as to eliminate the possibility of your code being subject to SQL injection attacks. Both string and integer placeholders are supported. Binding parameters can simply be achieved as follows:

use Store\Toys\Robots;

// Query robots binding parameters with string placeholders // Parameters whose keys are the same as placeholders $robots= Robots::find( [ "name = :name: AND type = :type:", "bind" =>[ "name" =>"Robotina", "type" =>"maid", ], ] );

// Query robots binding parameters with integer placeholders $robots= Robots::find( [ "name = ?1 AND type = ?2", "bind" =>[ 1 =>"Robotina", 2 =>"maid", ], ] );

// Query robots binding parameters with both string and integer placeholders // Parameters whose keys are the same as placeholders $robots= Robots::find( [ "name = :name: AND type = ?1", "bind" =>[ "name" =>"Robotina", 1 =>"maid", ], ] );

When using numeric placeholders, you will need to define them as integers i.e. 1 or 2. In this case “1” or “2” are considered strings and not numbers, so the placeholder could not be successfully replaced. Strings are automatically escaped using PDO. This function takes into account the connection charset, so its recom- mended to define the correct charset in the connection parameters or in the database configuration, as a wrong charset will produce undesired effects when storing or retrieving data. Additionally you can set the parameter “bindTypes”, this allows defining how the parameters should be bound accord- ing to its data type:

use Phalcon\Db\Column;

2.3. Components 133 Phalcon PHP Framework Documentation, Release 3.1.1

use Store\Toys\Robots;

// Bind parameters $parameters=[ "name" =>"Robotina", "year" => 2008, ];

// Casting Types $types=[ "name" => Column::BIND_PARAM_STR, "year" => Column::BIND_PARAM_INT, ];

// Query robots binding parameters with string placeholders $robots= Robots::find( [ "name = :name: AND year = :year:", "bind" => $parameters, "bindTypes" => $types, ] );

Since the default bind-type is Phalcon\Db\Column::BIND_PARAM_STR, there is no need to spec- ify the “bindTypes” parameter if all of the columns are of that type. If you bind arrays in bound parameters, keep in mind, that keys must be numbered from zero:

$array=["a","b","c"]; // $array: [[0] => "a", [1] => "b", [2] => "c"] unset($array[1]); // $array: [[0] => "a", [2] => "c"]

// Now we have to renumber the keys $array= array_values($array); // $array: [[0] => "a", [1] => "c"]

$robots= Robots::find( [ 'letter IN ({letter:array})', 'bind' =>[ 'letter' => $array ] ] );

Bound parameters are available for all query methods such as find() and findFirst() but also the calculation methods like count(), sum(), average() etc. If you’re using “finders”, bound parameters are automatically used for you:

// Explicit query using bound parameters

134 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

$robots= Robots::find( [ "name = ?0", "bind" =>[ "Ultron", ], ] );

// Implicit query using bound parameters $robots= Robots::findByName("Ultron");

Initializing/Preparing fetched records

May be the case that after obtaining a record from the database is necessary to initialise the data before being used by the rest of the application. You can implement the afterFetch() method in a model, this event will be executed just after create the instance and assign the data to it:

public $name;

public $status;

public function beforeSave() { // Convert the array into a string $this->status= join(",", $this->status); }

public function afterFetch() { // Convert the string to an array $this->status= explode(",", $this->status); }

public function afterSave() { // Convert the string to an array $this->status= explode(",", $this->status); } }

If you use getters/setters instead of/or together with public properties, you can initialize the field once it is accessed:

2.3. Components 135 Phalcon PHP Framework Documentation, Release 3.1.1

use Phalcon\Mvc\Model; class Robots extends Model { public $id;

public $name;

public $status;

public function getStatus() { return explode(",", $this->status); } }

Generating Calculations

Calculations (or aggregations) are helpers for commonly used functions of database systems such as COUNT, SUM, MAX, MIN or AVG. Phalcon\Mvc\Model allows to use these functions directly from the exposed methods. Count examples:

// How many employees are? $rowcount= Employees::count();

// How many different areas are assigned to employees? $rowcount= Employees::count( [ "distinct" =>"area", ] );

// How many employees are in the Testing area? $rowcount= Employees::count( "area = 'Testing'" );

// Count employees grouping results by their area $group= Employees::count( [ "group" =>"area", ] ); foreach ($group as $row){ echo "There are", $row->rowcount," in", $row->area; }

// Count employees grouping by their area and ordering the result by count $group= Employees::count( [ "group" =>"area", "order" =>"rowcount", ]

136 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

);

// Avoid SQL injections using bound parameters $group= Employees::count( [ "type > ?0", "bind" =>[ $type ], ] );

Sum examples:

// How much are the salaries of all employees? $total= Employees::sum( [ "column" =>"salary", ] );

// How much are the salaries of all employees in the Sales area? $total= Employees::sum( [ "column" =>"salary", "conditions" =>"area = 'Sales'", ] );

// Generate a grouping of the salaries of each area $group= Employees::sum( [ "column" =>"salary", "group" =>"area", ] ); foreach ($group as $row){ echo "The sum of salaries of the", $row->area," is", $row->sumatory; }

// Generate a grouping of the salaries of each area ordering // salaries from higher to lower $group= Employees::sum( [ "column" =>"salary", "group" =>"area", "order" =>"sumatory DESC", ] );

// Avoid SQL injections using bound parameters $group= Employees::sum( [ "conditions" =>"area > ?0", "bind" =>[ $area

2.3. Components 137 Phalcon PHP Framework Documentation, Release 3.1.1

], ] );

Average examples:

// What is the average salary for all employees? $average= Employees::average( [ "column" =>"salary", ] );

// What is the average salary for the Sales's area employees? $average= Employees::average( [ "column" =>"salary", "conditions" =>"area = 'Sales'", ] );

// Avoid SQL injections using bound parameters $average= Employees::average( [ "column" =>"age", "conditions" =>"area > ?0", "bind" =>[ $area ], ] );

Max/Min examples:

// What is the oldest age of all employees? $age= Employees::maximum( [ "column" =>"age", ] );

// What is the oldest of employees from the Sales area? $age= Employees::maximum( [ "column" =>"age", "conditions" =>"area = 'Sales'", ] );

// What is the lowest salary of all employees? $salary= Employees::minimum( [ "column" =>"salary", ]

138 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

);

Creating/Updating Records

The Phalcon\Mvc\Model::save() method allows you to create/update records according to whether they al- ready exist in the table associated with a model. The save method is called internally by the create and update methods of Phalcon\Mvc\Model. For this to work as expected it is necessary to have properly defined a primary key in the entity to determine whether a record should be updated or created. Also the method executes associated validators, virtual foreign keys and events that are defined in the model:

$robot= new Robots();

$robot->type="mechanical"; $robot->name="Astro Boy"; $robot->year= 1952; if ($robot->save() === false){ echo "Umh, We can't store robots right now: \n";

$messages= $robot->getMessages();

foreach ($messages as $message){ echo $message," \n"; } } else { echo "Great, a new robot was saved successfully!"; }

An array could be passed to “save” to avoid assign every column manually. Phalcon\Mvc\Model will check if there are setters implemented for the columns passed in the array giving priority to them instead of assign directly the values of the attributes:

$robot= new Robots();

$robot->save( [ "type" =>"mechanical", "name" =>"Astro Boy", "year" => 1952, ] );

Values assigned directly or via the array of attributes are escaped/sanitized according to the related attribute data type. So you can pass an insecure array without worrying about possible SQL injections:

2.3. Components 139 Phalcon PHP Framework Documentation, Release 3.1.1

use Store\Toys\Robots;

$robot= new Robots();

$robot->save($_POST);

Without precautions mass assignment could allow attackers to set any database column’s value. Only use this feature if you want to permit a user to insert/update every column in the model, even if those fields are not in the submitted form. You can set an additional parameter in ‘save’ to set a whitelist of fields that only must taken into account when doing the mass assignment:

$robot= new Robots();

$robot->save( $_POST, [ "name", "type", ] );

Create/Update with Confidence

When an application has a lot of competition, we could be expecting create a record but it is actually updated. This could happen if we use Phalcon\Mvc\Model::save() to persist the records in the database. If we want to be absolutely sure that a record is created or updated, we can change the save() call with create() or update():

$robot= new Robots();

$robot->type="mechanical"; $robot->name="Astro Boy"; $robot->year= 1952;

// This record only must be created if ($robot->create() === false){ echo "Umh, We can't store robots right now: \n";

$messages= $robot->getMessages();

foreach ($messages as $message){ echo $message," \n"; } } else { echo "Great, a new robot was created successfully!"; }

140 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

These methods “create” and “update” also accept an array of values as parameter.

Deleting Records

The Phalcon\Mvc\Model::delete() method allows to delete a record. You can use it as follows:

$robot= Robots::findFirst(11); if ($robot !== false){ if ($robot->delete() === false){ echo "Sorry, we can't delete the robot right now: \n";

$messages= $robot->getMessages();

foreach ($messages as $message){ echo $message," \n"; } } else { echo "The robot was deleted successfully!"; } }

You can also delete many records by traversing a resultset with a foreach:

$robots= Robots::find( "type = 'mechanical'" ); foreach ($robots as $robot){ if ($robot->delete() === false){ echo "Sorry, we can't delete the robot right now: \n";

$messages= $robot->getMessages();

foreach ($messages as $message){ echo $message," \n"; } } else { echo "The robot was deleted successfully!"; } }

The following events are available to define custom business rules that can be executed when a delete operation is performed: Operation Name Can stop operation? Explanation Deleting beforeDelete YES Runs before the delete operation is made Deleting afterDelete NO Runs after the delete operation was made With the above events can also define business rules in the models:

2.3. Components 141 Phalcon PHP Framework Documentation, Release 3.1.1

status ==="A"){ echo "The robot is active, it can't be deleted";

return false; }

return true; } }

Model Relationships

Relationships between Models

There are four types of relationships: one-on-one, one-to-many, many-to-one and many-to-many. The relationship may be unidirectional or bidirectional, and each can be simple (a one to one model) or more complex (a combination of models). The model manager manages foreign key constraints for these relationships, the definition of these helps referential integrity as well as easy and fast access of related records to a model. Through the implementation of relations, it is easy to access data in related models from each record in a uniform way.

Unidirectional relationships

Unidirectional relations are those that are generated in relation to one another but not vice versa.

Bidirectional relations

The bidirectional relations build relationships in both models and each model defines the inverse relationship of the other.

Defining relationships

In Phalcon, relationships must be defined in the initialize() method of a model. The methods belongsTo(), hasOne(), hasMany() and hasManyToMany() define the relationship between one or more fields from the current model to fields in another model. Each of these methods requires 3 parameters: local fields, referenced model, referenced fields. Method Description hasMany Defines a 1-n relationship hasOne Defines a 1-1 relationship belongsTo Defines a n-1 relationship hasManyToMany Defines a n-n relationship

142 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

The following schema shows 3 tables whose relations will serve us as an example regarding relationships:

CREATE TABLE `robots`( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(70) NOT NULL, `type` varchar(32) NOT NULL, `year` int(11) NOT NULL, PRIMARY KEY (`id`) );

CREATE TABLE `robots_parts`( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `robots_id` int(10) NOT NULL, `parts_id` int(10) NOT NULL, `created_at` DATE NOT NULL, PRIMARY KEY (`id`), KEY `robots_id`(`robots_id`), KEY `parts_id`(`parts_id`) );

CREATE TABLE `parts`( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(70) NOT NULL, PRIMARY KEY (`id`) );

• The model “Robots” has many “RobotsParts”. • The model “Parts” has many “RobotsParts”. • The model “RobotsParts” belongs to both “Robots” and “Parts” models as a many-to-one relation. • The model “Robots” has a relation many-to-many to “Parts” through “RobotsParts”. Check the EER diagram to understand better the relations: The models with their relations could be implemented as follows:

public $name;

public function initialize() { $this->hasMany( "id", "RobotsParts", "robots_id" ); } }

2.3. Components 143 Phalcon PHP Framework Documentation, Release 3.1.1

144 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

public $name;

public function initialize() { $this->hasMany( "id", "RobotsParts", "parts_id" ); } }

public $robots_id;

public $parts_id;

public function initialize() { $this->belongsTo( "robots_id", "Store\\Toys\\Robots", "id" );

$this->belongsTo( "parts_id", "Parts", "id" ); } }

The first parameter indicates the field of the local model used in the relationship; the second indicates the name of the referenced model and the third the field name in the referenced model. You could also use arrays to define multiple fields in the relationship. Many to many relationships require 3 models and define the attributes involved in the relationship:

2.3. Components 145 Phalcon PHP Framework Documentation, Release 3.1.1

use Phalcon\Mvc\Model; class Robots extends Model { public $id;

public $name;

public function initialize() { $this->hasManyToMany( "id", "RobotsParts", "robots_id","parts_id", "Parts", "id" ); } }

Taking advantage of relationships

When explicitly defining the relationships between models, it is easy to find related records for a particular record.

$robot= Robots::findFirst(2); foreach ($robot->robotsParts as $robotPart){ echo $robotPart->parts->name," \n"; }

Phalcon uses the magic methods __set/__get/__call to store or retrieve related data using relationships. By accessing an attribute with the same name as the relationship will retrieve all its related record(s).

$robot= Robots::findFirst();

// All the related records in RobotsParts $robotsParts= $robot->robotsParts;

Also, you can use a magic getter:

$robot= Robots::findFirst();

// All the related records in RobotsParts

146 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

$robotsParts= $robot->getRobotsParts();

// Passing parameters $robotsParts= $robot->getRobotsParts( [ "limit" =>5, ] );

If the called method has a “get” prefix Phalcon\Mvc\Model will return a findFirst()/find() result. The follow- ing example compares retrieving related results with using magic methods and without:

$robot= Robots::findFirst(2);

// Robots model has a 1-n (hasMany) // relationship to RobotsParts then $robotsParts= $robot->robotsParts;

// Only parts that match conditions $robotsParts= $robot->getRobotsParts( [ "created_at = :date:", "bind" =>[ "date" =>"2015-03-15" ] ] );

$robotPart= RobotsParts::findFirst(1);

// RobotsParts model has a n-1 (belongsTo) // relationship to RobotsParts then $robot= $robotPart->robots;

Getting related records manually:

$robot= Robots::findFirst(2);

// Robots model has a 1-n (hasMany) // relationship to RobotsParts, then $robotsParts= RobotsParts::find( [ "robots_id = :id:", "bind" =>[ "id" => $robot->id, ] ] );

// Only parts that match conditions

2.3. Components 147 Phalcon PHP Framework Documentation, Release 3.1.1

$robotsParts= RobotsParts::find( [ "robots_id = :id: AND created_at = :date:", "bind" =>[ "id" => $robot->id, "date" =>"2015-03-15", ] ] );

$robotPart= RobotsParts::findFirst(1);

// RobotsParts model has a n-1 (belongsTo) // relationship to RobotsParts then $robot= Robots::findFirst( [ "id = :id:", "bind" =>[ "id" => $robotPart->robots_id, ] ] );

The prefix “get” is used to find()/findFirst() related records. Depending on the type of relation it will use find() or findFirst(): Type Description Implicit Method Belongs-To Returns a model instance of the related record directly findFirst Has-One Returns a model instance of the related record directly findFirst Has-Many Returns a collection of model instances of the referenced model find Has-Many- Returns a collection of model instances of the referenced model, it implicitly does (complex to-Many ‘inner joins’ with the involved models query) You can also use the “count” prefix to return an integer denoting the count of the related records:

$robot= Robots::findFirst(2); echo "The robot has", $robot->countRobotsParts()," parts \n";

Aliasing Relationships

To explain better how aliases work, let’s check the following example: The “robots_similar” table has the function to define what robots are similar to others: mysql> desc robots_similar; +------+------+------+-----+------+------+ | Field | Type | Null | Key | Default | Extra | +------+------+------+-----+------+------+ | id | int(10) unsigned | NO | PRI | NULL | auto_increment | | robots_id | int(10) unsigned | NO | MUL | NULL | | | similar_robots_id | int(10) unsigned | NO | | NULL | |

148 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

+------+------+------+-----+------+------+ 3 rows in set(0.00 sec)

Both “robots_id” and “similar_robots_id” have a relation to the model Robots:

A model that maps this table and its relationships is the following:

belongsTo( "robots_id", "Store\\Toys\\Robots", "id" );

$this->belongsTo( "similar_robots_id", "Store\\Toys\\Robots", "id" ); } }

Since both relations point to the same model (Robots), obtain the records related to the relationship could not be clear:

$robotsSimilar= RobotsSimilar::findFirst();

// Returns the related record based on the column (robots_id) // Also as is a belongsTo it's only returning one record // but the name 'getRobots' seems to imply that return more than one $robot= $robotsSimilar->getRobots();

2.3. Components 149 Phalcon PHP Framework Documentation, Release 3.1.1

// but, how to get the related record based on the column (similar_robots_id) // if both relationships have the same name?

The aliases allow us to rename both relationships to solve these problems:

belongsTo( "robots_id", "Store\\Toys\\Robots", "id", [ "alias" =>"Robot", ] );

$this->belongsTo( "similar_robots_id", "Store\\Toys\\Robots", "id", [ "alias" =>"SimilarRobot", ] ); } }

With the aliasing we can get the related records easily:

$robotsSimilar= RobotsSimilar::findFirst();

// Returns the related record based on the column (robots_id) $robot= $robotsSimilar->getRobot(); $robot= $robotsSimilar->robot;

// Returns the related record based on the column (similar_robots_id) $similarRobot= $robotsSimilar->getSimilarRobot(); $similarRobot= $robotsSimilar->similarRobot;

Magic Getters vs. Explicit methods

Most IDEs and editors with auto-completion capabilities can not infer the correct types when using magic getters, instead of use the magic getters you can optionally define those methods explicitly with the corresponding docblocks helping the IDE to produce a better auto-completion:

150 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

namespace Store\Toys; use Phalcon\Mvc\Model; class Robots extends Model { public $id;

public $name;

public function initialize() { $this->hasMany( "id", "RobotsParts", "robots_id" ); }

/** * Return the related "robots parts" * * @return \RobotsParts[] */ public function getRobotsParts($parameters= null) { return $this->getRelated("RobotsParts", $parameters); } }

Virtual Foreign Keys

By default, relationships do not act like database foreign keys, that is, if you try to insert/update a value without having a valid value in the referenced model, Phalcon will not produce a validation message. You can modify this behavior by adding a fourth parameter when defining a relationship. The RobotsPart model can be changed to demonstrate this feature:

public $robots_id;

public $parts_id;

public function initialize() { $this->belongsTo( "robots_id", "Store\\Toys\\Robots", "id", [

2.3. Components 151 Phalcon PHP Framework Documentation, Release 3.1.1

"foreignKey" => true ] );

$this->belongsTo( "parts_id", "Parts", "id", [ "foreignKey" =>[ "message" =>"The part_id does not exist on the Parts model" ] ] ); } }

If you alter a belongsTo() relationship to act as foreign key, it will validate that the values inserted/updated on those fields have a valid value on the referenced model. Similarly, if a hasMany()/hasOne() is altered it will validate that the records cannot be deleted if that record is used on a referenced model.

hasMany( "id", "RobotsParts", "parts_id", [ "foreignKey" =>[ "message" =>"The part cannot be deleted because other robots are

˓→using it", ] ] ); } }

A virtual foreign key can be set up to allow null values as follows:

public $robots_id;

public $parts_id;

public function initialize()

152 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

{ $this->belongsTo( "parts_id", "Parts", "id", [ "foreignKey" =>[ "allowNulls" => true, "message" =>"The part_id does not exist on the Parts model", ] ] ); } }

Cascade/Restrict actions

Relationships that act as virtual foreign keys by default restrict the creation/update/deletion of records to maintain the integrity of data:

public $name;

public function initialize() { $this->hasMany( "id", "Parts", "robots_id", [ "foreignKey" =>[ "action" => Relation::ACTION_CASCADE, ] ] ); } }

The above code set up to delete all the referenced records (parts) if the master record (robot) is deleted.

Storing Related Records

Magic properties can be used to store a record and its related properties:

2.3. Components 153 Phalcon PHP Framework Documentation, Release 3.1.1

// Create an artist $artist= new Artists();

$artist->name="Shinichi Osawa"; $artist->country="Japan";

// Create an album $album= new Albums();

$album->name="The One"; $album->artist= $artist; // Assign the artist $album->year= 2008;

// Save both records $album->save();

Saving a record and its related records in a has-many relation:

// Get an existing artist $artist= Artists::findFirst( "name = 'Shinichi Osawa'" );

// Create an album $album= new Albums();

$album->name="The One"; $album->artist= $artist;

$songs= [];

// Create a first song $songs[0]= new Songs(); $songs[0]->name="Star Guitar"; $songs[0]->duration="5:54";

// Create a second song $songs[1]= new Songs(); $songs[1]->name="Last Days"; $songs[1]->duration="4:29";

// Assign the songs array $album->songs= $songs;

// Save the album + its songs $album->save();

Saving the album and the artist at the same time implicitly makes use of a transaction so if anything goes wrong with saving the related records, the parent will not be saved either. Messages are passed back to the user for information regarding any errors. Note: Adding related entities by overloading the following methods is not possible: • Phalcon\Mvc\Model::beforeSave()

154 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

• Phalcon\Mvc\Model::beforeCreate() • Phalcon\Mvc\Model::beforeUpdate() You need to overload Phalcon\Mvc\Model::save() for this to work from within a model.

Operations over Resultsets

If a resultset is composed of complete objects, the resultset is in the ability to perform operations on the records obtained in a simple manner:

Updating related records

Instead of doing this:

$parts= $robots->getParts();

foreach ($parts as $part){ $part->stock= 100; $part->updated_at= time();

if ($part->update() === false){ $messages= $part->getMessages();

foreach ($messages as $message){ echo $message; }

break; } }

you can do this:

$robots->getParts()->update( [ "stock" => 100, "updated_at" => time(), ] );

‘update’ also accepts an anonymous function to filter what records must be updated:

$data=[ "stock" => 100, "updated_at" => time(), ];

// Update all the parts except those whose type is basic $robots->getParts()->update( $data,

2.3. Components 155 Phalcon PHP Framework Documentation, Release 3.1.1

function ($part){ if ($part->type === Part::TYPE_BASIC){ return false; }

return true; } );

Deleting related records

Instead of doing this:

$parts= $robots->getParts(); foreach ($parts as $part){ if ($part->delete() === false){ $messages= $part->getMessages();

foreach ($messages as $message){ echo $message; }

break; } } you can do this:

$robots->getParts()->delete(); delete() also accepts an anonymous function to filter what records must be deleted:

// Delete only whose stock is greater or equal than zero $robots->getParts()->delete( function ($part){ if ($part->stock<0){ return false; }

return true; } );

Model Events

156 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Events and Events Manager

Models allow you to implement events that will be thrown while performing an insert/update/delete which can be used to define business rules. The following are the events supported by Phalcon\Mvc\Model and their order of execution: Opera- Name Can stop Explanation tion operation? Insert- beforeValida- YES Is executed before the fields are validated for not nulls/empty ing/Updatingtion strings or foreign keys Inserting beforeValida- YES Is executed before the fields are validated for not nulls/empty tionOnCreate strings or foreign keys when an insertion operation is being made Updating beforeValida- YES Is executed before the fields are validated for not nulls/empty tionOnUpdate strings or foreign keys when an updating operation is being made Insert- onValidation- YES Is executed after an integrity validator fails ing/UpdatingFails (already stopped) Inserting afterValida- YES Is executed after the fields are validated for not nulls/empty strings tionOnCreate or foreign keys when an insertion operation is being made Updating afterValida- YES Is executed after the fields are validated for not nulls/empty strings tionOnUpdate or foreign keys when an updating operation is being made Insert- afterValida- YES Is executed after the fields are validated for not nulls/empty strings ing/Updatingtion or foreign keys Insert- beforeSave YES Runs before the required operation over the database system ing/Updating Updating beforeUpdate YES Runs before the required operation over the database system only when an updating operation is being made Inserting beforeCreate YES Runs before the required operation over the database system only when an inserting operation is being made Updating afterUpdate NO Runs after the required operation over the database system only when an updating operation is being made Inserting afterCreate NO Runs after the required operation over the database system only when an inserting operation is being made Insert- afterSave NO Runs after the required operation over the database system ing/Updating

Implementing Events in the Model’s class

The easier way to make a model react to events is to implement a method with the same name of the event in the model’s class:

2.3. Components 157 Phalcon PHP Framework Documentation, Release 3.1.1

Events can be used to assign values before performing an operation, for example:

created_at= date("Y-m-d H:i:s"); }

public function beforeUpdate() { // Set the modification date $this->modified_in= date("Y-m-d H:i:s"); } }

Using a custom Events Manager

Additionally, this component is integrated with Phalcon\Events\Manager, this means we can create listeners that run when an event is triggered.

// Attach an anonymous function as a listener for "model" events $eventsManager->attach( "model:beforeSave", function (Event $event, $robot){ if ($robot->name ==="Scooby Doo"){ echo "Scooby Doo isn't a robot!";

return false; }

return true; } );

// Attach the events manager to the event $this->setEventsManager($eventsManager); }

158 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

}

In the example given above, the Events Manager only acts as a bridge between an object and a listener (the anonymous function). Events will be fired to the listener when ‘robots’ are saved:

$robot= new Robots();

$robot->name="Scooby Doo"; $robot->year= 1969;

$robot->save();

If we want all objects created in our application use the same EventsManager, then we need to assign it to the Models Manager:

// Registering the modelsManager service $di->setShared( "modelsManager", function () { $eventsManager= new EventsManager();

// Attach an anonymous function as a listener for "model" events $eventsManager->attach( "model:beforeSave", function (Event $event, $model){ // Catch events produced by the Robots model if (get_class($model) ==="Store \\Toys\\Robots"){ if ($model->name ==="Scooby Doo"){ echo "Scooby Doo isn't a robot!";

return false; } }

return true; } );

// Setting a default EventsManager $modelsManager= new ModelsManager();

$modelsManager->setEventsManager($eventsManager);

return $modelsManager; } );

If a listener returns false that will stop the operation that is executing currently.

2.3. Components 159 Phalcon PHP Framework Documentation, Release 3.1.1

Logging Low-Level SQL Statements

When using high-level abstraction components such as Phalcon\Mvc\Model to access a database, it is difficult to understand which statements are finally sent to the database system. Phalcon\Mvc\Model is supported internally by Phalcon\Db. Phalcon\Logger interacts with Phalcon\Db, providing logging capabilities on the database abstraction layer, thus allowing us to log SQL statements as they happen.

$di->set( "db", function () { $eventsManager= new EventsManager();

$logger= new FileLogger("app/logs/debug.log");

// Listen all the database events $eventsManager->attach( "db:beforeQuery", function ($event, $connection) use ($logger){ $logger->log( $connection->getSQLStatement(), Logger::INFO ); } );

$connection= new Connection( [ "host" =>"localhost", "username" =>"root", "password" =>"secret", "dbname" =>"invo", ] );

// Assign the eventsManager to the db adapter instance $connection->setEventsManager($eventsManager);

return $connection; } );

As models access the default database connection, all SQL statements that are sent to the database system will be logged in the file:

$robot= new Robots();

$robot->name="Robby the Robot";

160 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

$robot->created_at="1956-07-21"; if ($robot->save() === false){ echo "Cannot save robot"; }

As above, the file app/logs/db.log will contain something like this:

[Mon, 30 Apr 12 13:47:18 -0500][DEBUG][Resource Id #77] INSERT INTO robots (name, created_at) VALUES ('Robby the Robot', '1956-07-21')

Profiling SQL Statements

Thanks to Phalcon\Db, the underlying component of Phalcon\Mvc\Model, it’s possible to profile the SQL statements generated by the ORM in order to analyze the performance of database operations. With this you can diagnose performance problems and to discover bottlenecks.

$di->set( "profiler", function () { return new ProfilerDb(); }, true );

$di->set( "db", function () use ($di){ $eventsManager= new EventsManager();

// Get a shared instance of the DbProfiler $profiler= $di->getProfiler();

// Listen all the database events $eventsManager->attach( "db", function ($event, $connection) use ($profiler){ if ($event->getType() ==="beforeQuery"){ $profiler->startProfile( $connection->getSQLStatement() ); }

if ($event->getType() ==="afterQuery"){ $profiler->stopProfile(); } } );

$connection= new MysqlPdo(

2.3. Components 161 Phalcon PHP Framework Documentation, Release 3.1.1

[ "host" =>"localhost", "username" =>"root", "password" =>"secret", "dbname" =>"invo", ] );

// Assign the eventsManager to the db adapter instance $connection->setEventsManager($eventsManager);

return $connection; } );

Profiling some queries:

// Send some SQL statements to the database Robots::find();

Robots::find( [ "order" =>"name", ] );

Robots::find( [ "limit" => 30, ] );

// Get the generated profiles from the profiler $profiles= $di->get("profiler")->getProfiles(); foreach ($profiles as $profile){ echo "SQL Statement:", $profile->getSQLStatement()," \n"; echo "Start Time:", $profile->getInitialTime()," \n"; echo "Final Time:", $profile->getFinalTime()," \n"; echo "Total Elapsed Time:", $profile->getTotalElapsedSeconds()," \n"; }

Each generated profile contains the duration in milliseconds that each instruction takes to complete as well as the generated SQL statement.

Model Behaviors

Behaviors are shared conducts that several models may adopt in order to re-use code, the ORM provides an API to implement behaviors in your models. Also, you can use the events and callbacks as seen before as an alternative to implement Behaviors with more freedom. A behavior must be added in the model initializer, a model can have zero or more behaviors:

162 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

public $name;

public $created_at;

public function initialize() { $this->addBehavior( new Timestampable( [ "beforeCreate" =>[ "field" =>"created_at", "format" =>"Y-m-d", ] ] ) ); } }

The following built-in behaviors are provided by the framework: Name Description Timestam- Allows to automatically update a model’s attribute saving the datetime when a record is created pable or updated SoftDelete Instead of permanently delete a record it marks the record as deleted changing the value of a flag column

Timestampable

This behavior receives an array of options, the first level key must be an event name indicating when the column must be assigned:

addBehavior( new Timestampable( [ "beforeCreate" =>[ "field" =>"created_at", "format" =>"Y-m-d", ] ] )

2.3. Components 163 Phalcon PHP Framework Documentation, Release 3.1.1

); }

Each event can have its own options, ‘field’ is the name of the column that must be updated, if ‘format’ is a string it will be used as format of the PHP’s function date, format can also be an anonymous function providing you the free to generate any kind timestamp:

addBehavior( new Timestampable( [ "beforeCreate" =>[ "field" =>"created_at", "format" => function () { $datetime= new Datetime( new DateTimeZone("Europe/Stockholm") );

return $datetime->format("Y-m-d H:i:sP"); } ] ] ) ); }

If the option ‘format’ is omitted a timestamp using the PHP’s function time, will be used.

SoftDelete

This behavior can be used in the following way:

const NOT_DELETED="N";

public $id;

public $name;

public $status;

164 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

public function initialize() { $this->addBehavior( new SoftDelete( [ "field" =>"status", "value" => Users::DELETED, ] ) ); } }

This behavior accepts two options: ‘field’ and ‘value’, ‘field’ determines what field must be updated and ‘value’ the value to be deleted. Let’s pretend the table ‘users’ has the following data: mysql> select * from users; +----+------+------+ | id | name | status | +----+------+------+ |1 | Lana | N | |2 | Brandon | N | +----+------+------+ 2 rows in set(0.00 sec)

If we delete any of the two records the status will be updated instead of delete the record:

Users::findFirst(2)->delete();

The operation will result in the following data in the table: mysql> select * from users; +----+------+------+ | id | name | status | +----+------+------+ |1 | Lana | N | |2 | Brandon | D | +----+------+------+ 2 rows in set(0.01 sec)

Note that you need to specify the deleted condition in your queries to effectively ignore them as deleted records, this behavior doesn’t support that.

Creating your own behaviors

The ORM provides an API to create your own behaviors. A behavior must be a class implementing the Phal- con\Mvc\Model\BehaviorInterface. Also, Phalcon\Mvc\Model\Behavior provides most of the methods needed to ease the implementation of behaviors. The following behavior is an example, it implements the Blameable behavior which helps identify the user that is performed operations over a model:

2.3. Components 165 Phalcon PHP Framework Documentation, Release 3.1.1

case "afterCreate": case "afterDelete": case "afterUpdate":

$userName= // ... get the current user from session

// Store in a log the username, event type and primary key file_put_contents( "logs/blamable-log.txt", $userName."". $eventType."". $model->id );

break;

default: /* ignore the rest of events */ } } }

The former is a very simple behavior, but it illustrates how to create a behavior, now let’s add this behavior to a model:

addBehavior( new Blameable() ); } }

A behavior is also capable of intercepting missing methods on your models:

166 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

{ // If the method is 'getSlug' convert the title if ($method ==="getSlug"){ return Tag::friendlyTitle($model->title); } } }

Call that method on a model that implements Sluggable returns a SEO friendly title:

$title= $post->getSlug();

Using Traits as behaviors

Starting from PHP 5.4 you can use Traits to re-use code in your classes, this is another way to implement custom behaviors. The following trait implements a simple version of the Timestampable behavior:

trait MyTimestampable { public function beforeCreate() { $this->created_at= date("r"); }

public function beforeUpdate() { $this->updated_at= date("r"); } }

Then you can use it in your model as follows:

use Phalcon\Mvc\Model;

class Products extends Model { use MyTimestampable; }

Models Metadata

To speed up development Phalcon\Mvc\Model helps you to query fields and constraints from tables related to models. To achieve this, Phalcon\Mvc\Model\MetaData is available to manage and cache table metadata. Sometimes it is necessary to get those attributes when working with models. You can get a metadata instance as follows:

2.3. Components 167 Phalcon PHP Framework Documentation, Release 3.1.1

$robot= new Robots();

// Get Phalcon\Mvc\Model\Metadata instance $metadata= $robot->getModelsMetaData();

// Get robots fields names $attributes= $metadata->getAttributes($robot); print_r($attributes);

// Get robots fields data types $dataTypes= $metadata->getDataTypes($robot); print_r($dataTypes);

Caching Metadata

Once the application is in a production stage, it is not necessary to query the metadata of the table from the database system each time you use the table. This could be done caching the metadata using any of the following adapters: AdapterDescription API Mem- This adapter is the default. The metadata is cached only during the request. When Phal- ory the request is completed, the metadata are released as part of the normal memory of con\Mvc\Model\MetaData\Memory the request. This adapter is perfect when the application is in development so as to refresh the metadata in each request containing the new and/or modified fields. Ses- This adapter stores metadata in the $_SESSION superglobal. This adapter is Phal- sion recommended only when the application is actually using a small number of con\Mvc\Model\MetaData\Session models. The metadata are refreshed every time a new session starts. This also requires the use of session_start() to start the session before using any models. Apc This adapter uses the Alternative PHP Cache (APC) to store the table metadata. Phal- You can specify the lifetime of the metadata with options. This is the most con\Mvc\Model\MetaData\Apc recommended way to store metadata when the application is in production stage. XCacheThis adapter uses XCache to store the table metadata. You can specify the lifetime Phal- of the metadata with options. This is the most recommended way to store metadata con\Mvc\Model\MetaData\Xcache when the application is in production stage. Files This adapter uses plain files to store metadata. By using this adapter the Phal- disk-reading is increased but the database access is reduced. con\Mvc\Model\MetaData\Files As other ORM’s dependencies, the metadata manager is requested from the services container:

$di["modelsMetadata"]= function () { // Create a metadata manager with APC $metadata= new ApcMetaData( [ "lifetime" => 86400, "prefix" =>"my-prefix", ] );

return $metadata; };

168 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Metadata Strategies

As mentioned above the default strategy to obtain the model’s metadata is database introspection. In this strategy, the information schema is used to know the fields in a table, its primary key, nullable fields, data types, etc. You can change the default metadata introspection in the following way:

$di["modelsMetadata"]= function () { // Instantiate a metadata adapter $metadata= new ApcMetaData( [ "lifetime" => 86400, "prefix" =>"my-prefix", ] );

// Set a custom metadata introspection strategy $metadata->setStrategy( new MyIntrospectionStrategy() );

return $metadata; };

Database Introspection Strategy

This strategy doesn’t require any customization and is implicitly used by all the metadata adapters.

Annotations Strategy

This strategy makes use of annotations to describe the columns in a model:

/** * @Column(type="string", length=70, nullable=false) */ public $name;

/**

2.3. Components 169 Phalcon PHP Framework Documentation, Release 3.1.1

* @Column(type="string", length=32, nullable=false) */ public $type;

/** * @Column(type="integer", nullable=false) */ public $year; }

Annotations must be placed in properties that are mapped to columns in the mapped source. Properties without the @Column annotation are handled as simple class attributes. The following annotations are supported: Name Description Primary Mark the field as part of the table’s primary key Identity The field is an auto_increment/serial column Column This marks an attribute as a mapped column The annotation @Column supports the following parameters: Name Description type The column’s type (string, integer, decimal, boolean) length The column’s length if any nullable Set whether the column accepts null values or not The annotations strategy could be set up this way:

$di["modelsMetadata"]= function () { // Instantiate a metadata adapter $metadata= new ApcMetaData( [ "lifetime" => 86400, "prefix" =>"my-prefix", ] );

// Set a custom metadata database introspection $metadata->setStrategy( new StrategyAnnotations() );

return $metadata; };

Manual Metadata

Phalcon can obtain the metadata for each model automatically without the developer must set them manually using any of the introspection strategies presented above. The developer also has the option of define the metadata manually. This strategy overrides any strategy set in the metadata manager. New columns added/modified/removed to/from the mapped table must be added/modified/removed

170 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1 also for everything to work properly. The following example shows how to define the metadata manually:

[ "id", "name", "type", "year", ],

// Every column part of the primary key MetaData::MODELS_PRIMARY_KEY =>[ "id", ],

// Every column that isn't part of the primary key MetaData::MODELS_NON_PRIMARY_KEY =>[ "name", "type", "year", ],

// Every column that doesn't allows null values MetaData::MODELS_NOT_NULL =>[ "id", "name", "type", ],

// Every column and their data types MetaData::MODELS_DATA_TYPES =>[ "id" => Column::TYPE_INTEGER, "name" => Column::TYPE_VARCHAR, "type" => Column::TYPE_VARCHAR, "year" => Column::TYPE_INTEGER, ],

// The columns that have numeric data types MetaData::MODELS_DATA_TYPES_NUMERIC =>[ "id" => true, "year" => true, ],

// The identity column, use boolean false if the model doesn't have // an identity column MetaData::MODELS_IDENTITY_COLUMN =>"id",

2.3. Components 171 Phalcon PHP Framework Documentation, Release 3.1.1

// How every column must be bound/casted MetaData::MODELS_DATA_TYPES_BIND =>[ "id" => Column::BIND_PARAM_INT, "name" => Column::BIND_PARAM_STR, "type" => Column::BIND_PARAM_STR, "year" => Column::BIND_PARAM_INT, ],

// Fields that must be ignored from INSERT SQL statements MetaData::MODELS_AUTOMATIC_DEFAULT_INSERT =>[ "year" => true, ],

// Fields that must be ignored from UPDATE SQL statements MetaData::MODELS_AUTOMATIC_DEFAULT_UPDATE =>[ "year" => true, ],

// Default values for columns MetaData::MODELS_DEFAULT_VALUES =>[ "year" =>"2015", ],

// Fields that allow empty strings MetaData::MODELS_EMPTY_STRING_VALUES =>[ "name" => true, ], ); } }

Model Transactions

When a process performs multiple database operations, it might be important that each step is completed successfully so that data integrity can be maintained. Transactions offer the ability to ensure that all database operations have been executed successfully before the data is committed to the database. Transactions in Phalcon allow you to commit all operations if they were executed successfully or rollback all operations if something went wrong.

Manual Transactions

If an application only uses one connection and the transactions aren’t very complex, a transaction can be created by just moving the current connection into transaction mode and then commit or rollback the operation whether it is successful or not:

172 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

// Start a transaction $this->db->begin();

$robot= new Robots();

$robot->name="WALL·E"; $robot->created_at= date("Y-m-d");

// The model failed to save, so rollback the transaction if ($robot->save() === false){ $this->db->rollback(); return; }

$robotPart= new RobotParts();

$robotPart->robots_id= $robot->id; $robotPart->type="head";

// The model failed to save, so rollback the transaction if ($robotPart->save() === false){ $this->db->rollback();

return; }

// Commit the transaction $this->db->commit(); } }

Implicit Transactions

Existing relationships can be used to store records and their related instances, this kind of operation implicitly creates a transaction to ensure that data is correctly stored:

$robotPart= new RobotParts();

$robotPart->type="head";

$robot= new Robots();

$robot->name="WALL·E"; $robot->created_at= date("Y-m-d"); $robot->robotPart= $robotPart;

// Creates an implicit transaction to store both records $robot->save();

2.3. Components 173 Phalcon PHP Framework Documentation, Release 3.1.1

Isolated Transactions

Isolated transactions are executed in a new connection ensuring that all the generated SQL, virtual foreign key checks and business rules are isolated from the main connection. This kind of transaction requires a transaction manager that globally manages each transaction created ensuring that they are correctly rolled back/committed before ending the request:

// Request a transaction $transaction= $manager->get();

$robot= new Robots();

$robot->setTransaction($transaction);

$robot->name="WALL·E"; $robot->created_at= date("Y-m-d");

if ($robot->save() === false){ $transaction->rollback( "Cannot save robot" ); }

$robotPart= new RobotParts();

$robotPart->setTransaction($transaction);

$robotPart->robots_id= $robot->id; $robotPart->type="head";

if ($robotPart->save() === false){ $transaction->rollback( "Cannot save robot part" ); }

// Everything's gone fine, let's commit the transaction $transaction->commit(); } catch (TxFailed $e){ echo "Failed, reason:", $e->getMessage(); }

Transactions can be used to delete many records in a consistent way:

174 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

try { // Create a transaction manager $manager= new TxManager();

// Request a transaction $transaction= $manager->get();

// Get the robots to be deleted $robots= Robots::find( "type = 'mechanical'" );

foreach ($robots as $robot){ $robot->setTransaction($transaction);

// Something's gone wrong, we should rollback the transaction if ($robot->delete() === false){ $messages= $robot->getMessages();

foreach ($messages as $message){ $transaction->rollback( $message->getMessage() ); } } }

// Everything's gone fine, let's commit the transaction $transaction->commit();

echo "Robots were deleted successfully!"; } catch (TxFailed $e){ echo "Failed, reason:", $e->getMessage(); }

Transactions are reused no matter where the transaction object is retrieved. A new transaction is generated only when a commit() or rollback() is performed. You can use the service container to create the global transaction manager for the entire application:

$di->setShared( "transactions", function () { return new TransactionManager(); } );

Then access it from a controller or view:

2.3. Components 175 Phalcon PHP Framework Documentation, Release 3.1.1

public function saveAction() { // Obtain the TransactionsManager from the services container $manager= $this->di->getTransactions();

// Or $manager= $this->transactions;

// Request a transaction $transaction= $manager->get();

// ... } }

While a transaction is active, the transaction manager will always return the same transaction across the application.

Validating Models

Validating Data Integrity

Phalcon\Mvc\Model provides several events to validate data and implement business rules. The special “validation” event allows us to call built-in validators over the record. Phalcon exposes a few built-in validators that can be used at this stage of validation. The following example shows how to use it:

$validator->add( "type", new InclusionIn( [ "domain" =>[ "Mechanical", "Virtual", ] ] ) );

$validator->add( "name",

176 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

new Uniqueness( [ "message" =>"The robot name must be unique", ] ) );

return $this->validate($validator); } }

The above example performs a validation using the built-in validator “InclusionIn”. It checks the value of the field “type” in a domain list. If the value is not included in the method then the validator will fail and return false. For more information on validators, see the Validation documentation. The idea of creating validators is make them reusable between several models. A validator can also be as simple as:

type ==="Old"){ $message= new Message( "Sorry, old robots are not allowed anymore", "type", "MyType" );

$this->appendMessage($message);

return false; }

return true; } }

Validation Messages

Phalcon\Mvc\Model has a messaging subsystem that provides a flexible way to output or store the validation messages generated during the insert/update processes. Each message is an instance of Phalcon\Mvc\Model\Message and the set of messages generated can be retrieved with the getMessages() method. Each message provides extended information like the field name that generated the message or the message type:

save() === false){

2.3. Components 177 Phalcon PHP Framework Documentation, Release 3.1.1

$messages= $robot->getMessages();

foreach ($messages as $message){ echo "Message:", $message->getMessage(); echo "Field:", $message->getField(); echo "Type:", $message->getType(); } }

Phalcon\Mvc\Model can generate the following types of validation messages: Type Description PresenceOf Generated when a field with a non-null attribute on the database is trying to insert/update a null value ConstraintViola- Generated when a field part of a virtual foreign key is trying to insert/update a value that tion doesn’t exist in the referenced model InvalidValue Generated when a validator failed because of an invalid value InvalidCreateAt- Produced when a record is attempted to be created but it already exists tempt InvalidUp- Produced when a record is attempted to be updated but it doesn’t exist dateAttempt The getMessages() method can be overridden in a model to replace/translate the default messages generated automatically by the ORM:

foreach (parent::getMessages() as $message){ switch ($message->getType()) { case "InvalidCreateAttempt": $messages[]="The record cannot be created because it already

˓→exists"; break;

case "InvalidUpdateAttempt": $messages[]="The record cannot be updated because it doesn't

˓→exist"; break;

case "PresenceOf": $messages[]="The field". $message->getField()." is mandatory

˓→"; break; } }

return $messages; }

178 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

}

Validation Failed Events

Another type of events are available when the data validation process finds any inconsistency: Operation Name Explanation Insert or Update notSaved Triggered when the INSERT or UPDATE operation fails for any reason Insert, Delete or onValidation- Triggered when any data manipulation operation fails Update Fails

Working with Models (Advanced)

Hydration Modes

As mentioned previously, resultsets are collections of complete objects, this means that every returned result is an object representing a row in the database. These objects can be modified and saved again to persistence:

$robots= Robots::find();

// Manipulating a resultset of complete objects foreach ($robots as $robot){ $robot->year= 2000;

$robot->save(); }

Sometimes records are obtained only to be presented to a user in read-only mode, in these cases it may be useful to change the way in which records are represented to facilitate their handling. The strategy used to represent objects returned in a resultset is called ‘hydration mode’:

$robots= Robots::find();

// Return every robot as an array $robots->setHydrateMode( Resultset::HYDRATE_ARRAYS ); foreach ($robots as $robot){ echo $robot["year"], PHP_EOL; }

// Return every robot as a stdClass $robots->setHydrateMode(

2.3. Components 179 Phalcon PHP Framework Documentation, Release 3.1.1

Resultset::HYDRATE_OBJECTS ); foreach ($robots as $robot){ echo $robot->year, PHP_EOL; }

// Return every robot as a Robots instance $robots->setHydrateMode( Resultset::HYDRATE_RECORDS ); foreach ($robots as $robot){ echo $robot->year, PHP_EOL; }

Hydration mode can also be passed as a parameter of ‘find’:

$robots= Robots::find( [ "hydration" => Resultset::HYDRATE_ARRAYS, ] ); foreach ($robots as $robot){ echo $robot["year"], PHP_EOL; }

Auto-generated identity columns

Some models may have identity columns. These columns usually are the primary key of the mapped table. Phal- con\Mvc\Model can recognize the identity column omitting it in the generated SQL INSERT, so the database system can generate an auto-generated value for it. Always after creating a record, the identity field will be registered with the value generated in the database system for it:

$robot->save(); echo "The generated id is:", $robot->id;

Phalcon\Mvc\Model is able to recognize the identity column. Depending on the database system, those columns may be serial columns like in PostgreSQL or auto_increment columns in the case of MySQL. PostgreSQL uses sequences to generate auto-numeric values, by default, Phalcon tries to obtain the generated value from the sequence “table_field_seq”, for example: robots_id_seq, if that sequence has a different name, the getSequenceName() method needs to be implemented:

180 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

use Phalcon\Mvc\Model; class Robots extends Model { public function getSequenceName() { return "robots_sequence_name"; } }

Skipping Columns

To tell Phalcon\Mvc\Model that always omits some fields in the creation and/or update of records in order to delegate the database system the assignation of the values by a trigger or a default:

skipAttributes( [ "year", "price", ] );

// Skips only when inserting $this->skipAttributesOnCreate( [ "created_at", ] );

// Skips only when updating $this->skipAttributesOnUpdate( [ "modified_in", ] ); } }

This will ignore globally these fields on each INSERT/UPDATE operation on the whole application. If you want to ignore different attributes on different INSERT/UPDATE operations, you can specify the second parameter (boolean) - true for replacement. Forcing a default value can be done in the following way:

2.3. Components 181 Phalcon PHP Framework Documentation, Release 3.1.1

use Store\Toys\Robots; use Phalcon\Db\RawValue;

$robot= new Robots();

$robot->name="Bender"; $robot->year= 1999; $robot->created_at= new RawValue("default");

$robot->create();

A callback also can be used to create a conditional assignment of automatic default values:

price> 10000){ $this->type= new RawValue("default"); } } }

Never use a Phalcon\Db\RawValue to assign external data (such as user input) or variable data. The value of these fields is ignored when binding parameters to the query. So it could be used to attack the application injecting SQL.

Dynamic Update

SQL UPDATE statements are by default created with every column defined in the model (full all-field SQL update). You can change specific models to make dynamic updates, in this case, just the fields that had changed are used to create the final SQL statement. In some cases this could improve the performance by reducing the traffic between the application and the database server, this specially helps when the table has blob/text fields:

useDynamicUpdate(true); }

182 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

}

Independent Column Mapping

The ORM supports an independent column map, which allows the developer to use different column names in the model to the ones in the table. Phalcon will recognize the new column names and will rename them accordingly to match the respective columns in the database. This is a great feature when one needs to rename fields in the database without having to worry about all the queries in the code. A change in the column map in the model will take care of the rest. For example:

public $theName;

public $theType;

public $theYear;

public function columnMap() { // Keys are the real names in the table and // the values their names in the application return [ "id" =>"code", "the_name" =>"theName", "the_type" =>"theType", "the_year" =>"theYear", ]; } }

Then you can use the new names naturally in your code:

// Find a robot by its name $robot= Robots::findFirst( "theName = 'Voltron'" ); echo $robot->theName," \n";

// Get robots ordered by type $robot= Robots::find( [ "order" =>"theType DESC",

2.3. Components 183 Phalcon PHP Framework Documentation, Release 3.1.1

] ); foreach ($robots as $robot){ echo "Code:", $robot->code," \n"; }

// Create a robot $robot= new Robots();

$robot->code="10101"; $robot->theName="Bender"; $robot->theType="Industrial"; $robot->theYear= 2999;

$robot->save();

Take into consideration the following the next when renaming your columns: • References to attributes in relationships/validators must use the new names • Refer the real column names will result in an exception by the ORM The independent column map allow you to: • Write applications using your own conventions • Eliminate vendor prefixes/suffixes in your code • Change column names without change your application code

Record Snapshots

Specific models could be set to maintain a record snapshot when they’re queried. You can use this feature to implement auditing or just to know what fields are changed according to the data queried from the persistence:

keepSnapshots(true); } }

When activating this feature the application consumes a bit more of memory to keep track of the original values obtained from the persistence. In models that have this feature activated you can check what fields changed:

// Get a record from the database $robot= Robots::findFirst();

184 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

// Change a column $robot->name="Other name"; var_dump($robot->getChangedFields()); // ["name"] var_dump($robot->hasChanged("name")); // true var_dump($robot->hasChanged("type")); // false

Pointing to a different schema

If a model is mapped to a table that is in a different schemas/databases than the default. You can use the setSchema() method to define that:

setSchema("toys"); } }

Setting multiple databases

In Phalcon, all models can belong to the same database connection or have an individual one. Actually, when Phal- con\Mvc\Model needs to connect to the database it requests the “db” service in the application’s services container. You can overwrite this service setting it in the initialize() method:

// This service returns a MySQL database $di->set( "dbMysql", function () { return new MysqlPdo( [ "host" =>"localhost", "username" =>"root", "password" =>"secret", "dbname" =>"invo", ] ); } );

2.3. Components 185 Phalcon PHP Framework Documentation, Release 3.1.1

// This service returns a PostgreSQL database $di->set( "dbPostgres", function () { return new PostgreSQLPdo( [ "host" =>"localhost", "username" =>"postgres", "password" =>"", "dbname" =>"invo", ] ); } );

Then, in the initialize() method, we define the connection service for the model:

setConnectionService("dbPostgres"); } }

But Phalcon offers you more flexibility, you can define the connection that must be used to ‘read’ and for ‘write’. This is specially useful to balance the load to your databases implementing a master-slave architecture:

setReadConnectionService("dbSlave");

$this->setWriteConnectionService("dbMaster"); } }

The ORM also provides Horizontal Sharding facilities, by allowing you to implement a ‘shard’ selection according to the current query conditions:

186 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

class Robots extends Model { /** * Dynamically selects a shard * * @param array $intermediate * @param array $bindParams * @param array $bindTypes */ public function selectReadConnection($intermediate, $bindParams, $bindTypes) { // Check if there is a 'where' clause in the select if (isset($intermediate["where"])) { $conditions= $intermediate["where"];

// Choose the possible shard according to the conditions if ($conditions["left"]["name"] ==="id"){ $id= $conditions["right"]["value"];

if ($id>0&& $id< 10000){ return $this->getDI()->get("dbShard1"); }

if ($id> 10000){ return $this->getDI()->get("dbShard2"); } } }

// Use a default shard return $this->getDI()->get("dbShard0"); } }

The selectReadConnection() method is called to choose the right connection, this method intercepts any new query executed:

$robot= Robots::findFirst('id = 101');

Injecting services into Models

You may be required to access the application services within a model, the following example explains how to do that:

2.3. Components 187 Phalcon PHP Framework Documentation, Release 3.1.1

public function notSaved() { // Obtain the flash service from the DI container $flash= $this->getDI()->getFlash();

$messages= $this->getMessages();

// Show validation messages foreach ($messages as $message){ $flash->error($message); } } }

The “notSaved” event is triggered every time that a “create” or “update” action fails. So we’re flashing the validation messages obtaining the “flash” service from the DI container. By doing this, we don’t have to print messages after each save.

Disabling/Enabling Features

In the ORM we have implemented a mechanism that allow you to enable/disable specific features or options globally on the fly. According to how you use the ORM you can disable that you aren’t using. These options can also be temporarily disabled if required:

Model::setup( [ "events" => false, "columnRenaming" => false, ] );

The available options are: Option Description De- fault events Enables/Disables callbacks, hooks and event notifications from all the models true columnRenaming Enables/Disables the column renaming true notNullValida- The ORM automatically validate the not null columns present in the mapped table true tions virtualFor- Enables/Disables the virtual foreign keys true eignKeys phqlLiterals Enables/Disables literals in the PHQL parser true lateStateBinding Enables/Disables late state binding of the Mvc\Model::cloneResultMap() false method

Stand-Alone component

Using Phalcon\Mvc\Model in a stand-alone mode can be demonstrated below:

188 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

$di= new Di();

// Setup a connection $di->set( "db", new Connection( [ "dbname" =>"sample.db", ] ) );

// Set a models manager $di->set( "modelsManager", new ModelsManager() );

// Use the memory meta-data adapter or other $di->set( "modelsMetadata", new MetaData() );

// Create a model class Robots extends Model {

}

// Use the model echo Robots::count();

Phalcon Query Language (PHQL)

Phalcon Query Language, PhalconQL or simply PHQL is a high-level, object-oriented SQL dialect that allows to write queries using a standardized SQL-like language. PHQL is implemented as a parser (written in C) that translates syntax in that of the target RDBMS. To achieve the highest performance possible, Phalcon provides a parser that uses the same technology as SQLite. This technology provides a small in-memory parser with a very low memory footprint that is also thread-safe. The parser first checks the syntax of the pass PHQL statement, then builds an intermediate representation of the statement and finally it converts it to the respective SQL dialect of the target RDBMS. In PHQL, we’ve implemented a set of features to make your access to databases more secure: • Bound parameters are part of the PHQL language helping you to secure your code • PHQL only allows one SQL statement to be executed per call preventing injections

2.3. Components 189 Phalcon PHP Framework Documentation, Release 3.1.1

• PHQL ignores all SQL comments which are often used in SQL injections • PHQL only allows data manipulation statements, avoiding altering or dropping tables/databases by mistake or externally without authorization • PHQL implements a high-level abstraction allowing you to handle tables as models and fields as class attributes

Usage Example

To better explain how PHQL works consider the following example. We have two models “Cars” and “Brands”:

use Phalcon\Mvc\Model;

class Cars extends Model { public $id;

public $name;

public $brand_id;

public $price;

public $year;

public $style;

/** * This model is mapped to the table sample_cars */ public function getSource() { return "sample_cars"; }

/** * A car only has a Brand, but a Brand have many Cars */ public function initialize() { $this->belongsTo("brand_id","Brands","id"); } }

And every Car has a Brand, so a Brand has many Cars:

use Phalcon\Mvc\Model;

class Brands extends Model { public $id;

public $name;

190 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

/** * The model Brands is mapped to the "sample_brands" table */ public function getSource() { return "sample_brands"; }

/** * A Brand can have many Cars */ public function initialize() { $this->hasMany("id","Cars","brand_id"); } }

Creating PHQL Queries

PHQL queries can be created just by instantiating the class Phalcon\Mvc\Model\Query:

// Instantiate the Query $query= new Query( "SELECT * FROM Cars", $this->getDI() );

// Execute the query returning a result if any $cars= $query->execute();

From a controller or a view, it’s easy to create/execute them using an injected models manager:

// Executing a simple query $query= $this->modelsManager->createQuery("SELECT * FROM Cars"); $cars= $query->execute();

// With bound parameters $query= $this->modelsManager->createQuery("SELECT * FROM Cars WHERE name = :name:"); $cars= $query->execute( [ "name" =>"Audi", ] );

Or simply execute it:

// Executing a simple query $cars= $this->modelsManager->executeQuery(

2.3. Components 191 Phalcon PHP Framework Documentation, Release 3.1.1

"SELECT * FROM Cars" );

// Executing with bound parameters $cars= $this->modelsManager->executeQuery( "SELECT * FROM Cars WHERE name = :name:", [ "name" =>"Audi", ] );

Selecting Records

As the familiar SQL, PHQL allows querying of records using the SELECT statement we know, except that instead of specifying tables, we use the models classes:

$query= $manager->createQuery( "SELECT * FROM Cars ORDER BY Cars.name" );

$query= $manager->createQuery( "SELECT Cars.name FROM Cars ORDER BY Cars.name" );

Classes in namespaces are also allowed:

$phql="SELECT * FROM Formula\Cars ORDER BY Formula\Cars.name"; $query= $manager->createQuery($phql);

$phql="SELECT Formula\Cars.name FROM Formula\Cars ORDER BY Formula\Cars.name"; $query= $manager->createQuery($phql);

$phql="SELECT c.name FROM Formula\Cars c ORDER BY c.name"; $query= $manager->createQuery($phql);

Most of the SQL standard is supported by PHQL, even nonstandard directives such as LIMIT:

$phql="SELECT c.name FROM Cars AS c WHERE c.brand_id = 21 ORDER BY c.name LIMIT 100

˓→";

$query= $manager->createQuery($phql);

Result Types

Depending on the type of columns we query, the result type will vary. If you retrieve a single whole object, then the object returned is a Phalcon\Mvc\Model\Resultset\Simple. This kind of resultset is a set of complete model objects:

192 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

$phql="SELECT c. * FROM Cars AS c ORDER BY c.name";

$cars= $manager->executeQuery($phql); foreach ($cars as $car){ echo "Name:", $car->name," \n"; }

This is exactly the same as:

$cars= Cars::find( [ "order" =>"name" ] ); foreach ($cars as $car){ echo "Name:", $car->name," \n"; }

Complete objects can be modified and re-saved in the database because they represent a complete record of the asso- ciated table. There are other types of queries that do not return complete objects, for example:

$phql="SELECT c.id, c.name FROM Cars AS c ORDER BY c.name";

$cars= $manager->executeQuery($phql); foreach ($cars as $car){ echo "Name:", $car->name," \n"; }

We are only requesting some fields in the table, therefore those cannot be considered an entire object, so the returned object is still a resultset of type Phalcon\Mvc\Model\Resultset\Simple. However, each element is a standard object that only contain the two columns that were requested. These values that don’t represent complete objects are what we call scalars. PHQL allows you to query all types of scalars: fields, functions, literals, expressions, etc..:

$phql="SELECT CONCAT(c.id, ' ', c.name) AS id_name FROM Cars AS c ORDER BY c.name";

$cars= $manager->executeQuery($phql); foreach ($cars as $car){ echo $car->id_name," \n"; }

As we can query complete objects or scalars, we can also query both at once:

2.3. Components 193 Phalcon PHP Framework Documentation, Release 3.1.1

$phql="SELECT c.price *0.16 AS taxes, c.* FROM Cars AS c ORDER BY c.name";

$result= $manager->executeQuery($phql);

The result in this case is an object Phalcon\Mvc\Model\Resultset\Complex. This allows access to both complete objects and scalars at once:

cars->name," \n"; echo "Price:", $row->cars->price," \n"; echo "Taxes:", $row->taxes," \n"; }

Scalars are mapped as properties of each “row”, while complete objects are mapped as properties with the name of its related model.

Joins

It’s easy to request records from multiple models using PHQL. Most kinds of Joins are supported. As we defined relationships in the models, PHQL adds these conditions automatically:

$phql="SELECT Cars.name AS car_name, Brands.name AS brand_name FROM Cars JOIN Brands

˓→";

$rows= $manager->executeQuery($phql); foreach ($rows as $row){ echo $row->car_name," \n"; echo $row->brand_name," \n"; }

By default, an INNER JOIN is assumed. You can specify the type of JOIN in the query:

$phql="SELECT Cars. *, Brands.* FROM Cars INNER JOIN Brands"; $rows= $manager->executeQuery($phql);

$phql="SELECT Cars. *, Brands.* FROM Cars LEFT JOIN Brands"; $rows= $manager->executeQuery($phql);

$phql="SELECT Cars. *, Brands.* FROM Cars LEFT OUTER JOIN Brands"; $rows= $manager->executeQuery($phql);

$phql="SELECT Cars. *, Brands.* FROM Cars CROSS JOIN Brands"; $rows= $manager->executeQuery($phql);

It is also possible to manually set the conditions of the JOIN:

$phql="SELECT Cars. *, Brands.* FROM Cars INNER JOIN Brands ON Brands.id = Cars. ˓→brands_id";

194 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

$rows= $manager->executeQuery($phql);

Also, the joins can be created using multiple tables in the FROM clause:

$phql="SELECT Cars. *, Brands.* FROM Cars, Brands WHERE Brands.id = Cars.brands_id";

$rows= $manager->executeQuery($phql); foreach ($rows as $row){ echo "Car:", $row->cars->name," \n"; echo "Brand:", $row->brands->name," \n"; }

If an alias is used to rename the models in the query, those will be used to name the attributes in the every row of the result:

$phql="SELECT c. *, b.* FROM Cars c, Brands b WHERE b.id = c.brands_id";

$rows= $manager->executeQuery($phql); foreach ($rows as $row){ echo "Car:", $row->c->name," \n"; echo "Brand:", $row->b->name," \n"; }

When the joined model has a many-to-many relation to the ‘from’ model, the intermediate model is implicitly added to the generated query:

$phql="SELECT Artists.name, Songs.name FROM Artists". "JOIN Songs WHERE Artists.genre = 'Trip-Hop'";

$result= $this->modelsManager->executeQuery($phql);

This code executes the following SQL in MySQL:

SELECT `artists`.`name`,`songs`.`name` FROM `artists` INNER JOIN `albums` ON `albums`.`artists_id`=`artists`.`id` INNER JOIN `songs` ON `albums`.`songs_id`=`songs`.`id` WHERE `artists`.`genre`= 'Trip-Hop'

Aggregations

The following examples show how to use aggregations in PHQL:

// How much are the prices of all the cars? $phql="SELECT SUM(price) AS summatory FROM Cars"; $row= $manager->executeQuery($phql)->getFirst();

2.3. Components 195 Phalcon PHP Framework Documentation, Release 3.1.1

echo $row['summatory'];

// How many cars are by each brand? $phql="SELECT Cars.brand_id, COUNT( *) FROM Cars GROUP BY Cars.brand_id"; $rows= $manager->executeQuery($phql); foreach ($rows as $row){ echo $row->brand_id,'', $row["1"]," \n"; }

// How many cars are by each brand? $phql="SELECT Brands.name, COUNT( *) FROM Cars JOIN Brands GROUP BY 1"; $rows= $manager->executeQuery($phql); foreach ($rows as $row){ echo $row->name,'', $row["1"]," \n"; }

$phql="SELECT MAX(price) AS maximum, MIN(price) AS minimum FROM Cars"; $rows= $manager->executeQuery($phql); foreach ($rows as $row){ echo $row["maximum"],'', $row["minimum"]," \n"; }

// Count distinct used brands $phql="SELECT COUNT(DISTINCT brand_id) AS brandId FROM Cars"; $rows= $manager->executeQuery($phql); foreach ($rows as $row){ echo $row->brandId," \n"; }

Conditions

Conditions allow us to filter the set of records we want to query. The WHERE clause allows to do that:

// Simple conditions $phql="SELECT * FROM Cars WHERE Cars.name = 'Lamborghini Espada'"; $cars= $manager->executeQuery($phql);

$phql="SELECT * FROM Cars WHERE Cars.price > 10000"; $cars= $manager->executeQuery($phql);

$phql="SELECT * FROM Cars WHERE TRIM(Cars.name) = 'Audi R8'"; $cars= $manager->executeQuery($phql);

$phql="SELECT * FROM Cars WHERE Cars.name LIKE 'Ferrari%'"; $cars= $manager->executeQuery($phql);

$phql="SELECT * FROM Cars WHERE Cars.name NOT LIKE 'Ferrari%'"; $cars= $manager->executeQuery($phql);

$phql="SELECT * FROM Cars WHERE Cars.price IS NULL"; $cars= $manager->executeQuery($phql);

$phql="SELECT * FROM Cars WHERE Cars.id IN (120, 121, 122)"; $cars= $manager->executeQuery($phql);

196 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

$phql="SELECT * FROM Cars WHERE Cars.id NOT IN (430, 431)"; $cars= $manager->executeQuery($phql);

$phql="SELECT * FROM Cars WHERE Cars.id BETWEEN 1 AND 100"; $cars= $manager->executeQuery($phql);

Also, as part of PHQL, prepared parameters automatically escape the input data, introducing more security:

$phql="SELECT * FROM Cars WHERE Cars.name = :name:"; $cars= $manager->executeQuery( $phql, [ "name" =>"Lamborghini Espada" ] );

$phql="SELECT * FROM Cars WHERE Cars.name = ?0"; $cars= $manager->executeQuery( $phql, [ 0 =>"Lamborghini Espada" ] );

Inserting Data

With PHQL it’s possible to insert data using the familiar INSERT statement:

// Inserting without columns $phql="INSERT INTO Cars VALUES (NULL, 'Lamborghini Espada'," ."7, 10000.00, 1969, 'Grand Tourer')"; $manager->executeQuery($phql);

// Specifying columns to insert $phql="INSERT INTO Cars (name, brand_id, year, style)" ."VALUES ('Lamborghini Espada', 7, 1969, 'Grand Tourer')"; $manager->executeQuery($phql);

// Inserting using placeholders $phql="INSERT INTO Cars (name, brand_id, year, style)" ."VALUES (:name:, :brand_id:, :year:, :style)"; $manager->executeQuery( $phql, [ "name" =>"Lamborghini Espada", "brand_id" =>7, "year" => 1969, "style" =>"Grand Tourer", ] );

2.3. Components 197 Phalcon PHP Framework Documentation, Release 3.1.1

Phalcon doesn’t only transform the PHQL statements into SQL. All events and business rules defined in the model are executed as if we created individual objects manually. Let’s add a business rule on the model cars. A car cannot cost less than $ 10,000:

price< 10000){ $this->appendMessage( new Message("A car cannot cost less than$ 10,000") );

return false; } } }

If we made the following INSERT in the models Cars, the operation will not be successful because the price does not meet the business rule that we implemented. By checking the status of the insertion we can print any validation messages generated internally:

$phql="INSERT INTO Cars VALUES (NULL, 'Nissan Versa', 7, 9999.00, 2015, 'Sedan')";

$result= $manager->executeQuery($phql); if ($result->success() === false){ foreach ($result->getMessages() as $message){ echo $message->getMessage(); } }

Updating Data

Updating rows is very similar than inserting rows. As you may know, the instruction to update records is UPDATE. When a record is updated the events related to the update operation will be executed for each row.

// Updating a single column $phql="UPDATE Cars SET price = 15000.00 WHERE id = 101"; $manager->executeQuery($phql);

// Updating multiples columns $phql="UPDATE Cars SET price = 15000.00, type = 'Sedan' WHERE id = 101"; $manager->executeQuery($phql);

// Updating multiples rows $phql="UPDATE Cars SET price = 7000.00, type = 'Sedan' WHERE brands_id > 5"; $manager->executeQuery($phql);

198 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

// Using placeholders $phql="UPDATE Cars SET price = ?0, type = ?1 WHERE brands_id > ?2"; $manager->executeQuery( $phql, [ 0 => 7000.00, 1 => 'Sedan', 2 =>5, ] );

An UPDATE statement performs the update in two phases: • First, if the UPDATE has a WHERE clause it retrieves all the objects that match these criteria, • Second, based on the queried objects it updates/changes the requested attributes storing them to the relational database This way of operation allows that events, virtual foreign keys and validations take part of the updating process. In summary, the following code:

$phql="UPDATE Cars SET price = 15000.00 WHERE id > 101";

$result= $manager->executeQuery($phql); if ($result->success() === false){ $messages= $result->getMessages();

foreach ($messages as $message){ echo $message->getMessage(); } } is somewhat equivalent to:

$messages= null;

$process= function () use (&$messages){ $cars= Cars::find("id > 101");

foreach ($cars as $car){ $car->price= 15000;

if ($car->save() === false){ $messages= $car->getMessages();

return false; } }

return true; };

$success= $process();

2.3. Components 199 Phalcon PHP Framework Documentation, Release 3.1.1

Deleting Data

When a record is deleted the events related to the delete operation will be executed for each row:

// Deleting a single row $phql="DELETE FROM Cars WHERE id = 101"; $manager->executeQuery($phql);

// Deleting multiple rows $phql="DELETE FROM Cars WHERE id > 100"; $manager->executeQuery($phql);

// Using placeholders $phql="DELETE FROM Cars WHERE id BETWEEN :initial: AND :final:"; $manager->executeQuery( $phql, [ "initial" =>1, "final" => 100, ] );

DELETE operations are also executed in two phases like UPDATEs. To check if the deletion produces any validation messages you should check the status code returned:

// Deleting multiple rows $phql="DELETE FROM Cars WHERE id > 100";

$result= $manager->executeQuery($phql); if ($result->success() === false){ $messages= $result->getMessages();

foreach ($messages as $message){ echo $message->getMessage(); } }

Creating queries using the Query Builder

A builder is available to create PHQL queries without the need to write PHQL statements, also providing IDE facilities:

// Getting a whole set $robots= $this->modelsManager->createBuilder() ->from("Robots") ->join("RobotsParts") ->orderBy("Robots.name") ->getQuery() ->execute();

// Getting the first row

200 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

$robots= $this->modelsManager->createBuilder() ->from("Robots") ->join("RobotsParts") ->orderBy("Robots.name") ->getQuery() ->getSingleResult();

That is the same as:

$phql="SELECT Robots. * FROM Robots JOIN RobotsParts p ORDER BY Robots.name LIMIT 20 ˓→";

$result= $manager->executeQuery($phql);

More examples of the builder:

// 'SELECT Robots.* FROM Robots'; $builder->from("Robots");

// 'SELECT Robots.*, RobotsParts.* FROM Robots, RobotsParts'; $builder->from( [ "Robots", "RobotsParts", ] );

// 'SELECT * FROM Robots'; $phql= $builder->columns(" *") ->from("Robots");

// 'SELECT id FROM Robots'; $builder->columns("id") ->from("Robots");

// 'SELECT id, name FROM Robots'; $builder->columns(["id","name"]) ->from("Robots");

// 'SELECT Robots.* FROM Robots WHERE Robots.name = "Voltron"'; $builder->from("Robots") ->where("Robots.name = 'Voltron'");

// 'SELECT Robots.* FROM Robots WHERE Robots.id = 100'; $builder->from("Robots") ->where(100);

// 'SELECT Robots.* FROM Robots WHERE Robots.type = "virtual" AND Robots.id > 50'; $builder->from("Robots") ->where("type = 'virtual'") ->andWhere("id > 50");

// 'SELECT Robots.* FROM Robots WHERE Robots.type = "virtual" OR Robots.id > 50'; $builder->from("Robots")

2.3. Components 201 Phalcon PHP Framework Documentation, Release 3.1.1

->where("type = 'virtual'") ->orWhere("id > 50");

// 'SELECT Robots.* FROM Robots GROUP BY Robots.name'; $builder->from("Robots") ->groupBy("Robots.name");

// 'SELECT Robots.* FROM Robots GROUP BY Robots.name, Robots.id'; $builder->from("Robots") ->groupBy(["Robots.name","Robots.id"]);

// 'SELECT Robots.name, SUM(Robots.price) FROM Robots GROUP BY Robots.name'; $builder->columns(["Robots.name","SUM(Robots.price)"]) ->from("Robots") ->groupBy("Robots.name");

// 'SELECT Robots.name, SUM(Robots.price) FROM Robots GROUP BY Robots.name HAVING

˓→SUM(Robots.price) > 1000'; $builder->columns(["Robots.name","SUM(Robots.price)"]) ->from("Robots") ->groupBy("Robots.name") ->having("SUM(Robots.price) > 1000");

// 'SELECT Robots.* FROM Robots JOIN RobotsParts'; $builder->from("Robots") ->join("RobotsParts");

// 'SELECT Robots.* FROM Robots JOIN RobotsParts AS p'; $builder->from("Robots") ->join("RobotsParts", null,"p");

// 'SELECT Robots.* FROM Robots JOIN RobotsParts ON Robots.id = RobotsParts.robots_id ˓→AS p'; $builder->from("Robots") ->join("RobotsParts","Robots.id = RobotsParts.robots_id","p");

// 'SELECT Robots.* FROM Robots // JOIN RobotsParts ON Robots.id = RobotsParts.robots_id AS p // JOIN Parts ON Parts.id = RobotsParts.parts_id AS t'; $builder->from("Robots") ->join("RobotsParts","Robots.id = RobotsParts.robots_id","p") ->join("Parts","Parts.id = RobotsParts.parts_id","t");

// 'SELECT r.* FROM Robots AS r'; $builder->addFrom("Robots","r");

// 'SELECT Robots.*, p.* FROM Robots, Parts AS p'; $builder->from("Robots") ->addFrom("Parts","p");

// 'SELECT r.*, p.* FROM Robots AS r, Parts AS p'; $builder->from(["r" =>"Robots"]) ->addFrom("Parts","p");

// 'SELECT r.*, p.* FROM Robots AS r, Parts AS p'; $builder->from(["r" =>"Robots","p" =>"Parts"]);

// 'SELECT Robots.* FROM Robots LIMIT 10';

202 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

$builder->from("Robots") ->limit(10);

// 'SELECT Robots.* FROM Robots LIMIT 10 OFFSET 5'; $builder->from("Robots") ->limit(10,5);

// 'SELECT Robots.* FROM Robots WHERE id BETWEEN 1 AND 100'; $builder->from("Robots") ->betweenWhere("id",1, 100);

// 'SELECT Robots.* FROM Robots WHERE id IN (1, 2, 3)'; $builder->from("Robots") ->inWhere("id",[1,2,3]);

// 'SELECT Robots.* FROM Robots WHERE id NOT IN (1, 2, 3)'; $builder->from("Robots") ->notInWhere("id",[1,2,3]);

// 'SELECT Robots.* FROM Robots WHERE name LIKE '%Art%'; $builder->from("Robots") ->where("name LIKE :name:",["name" =>"%". $name."%"]);

// 'SELECT r.* FROM Store\Robots WHERE r.name LIKE '%Art%'; $builder->from(['r' => 'Store\Robots']) ->where("r.name LIKE :name:",["name" =>"%". $name."%"]);

Bound Parameters

Bound parameters in the query builder can be set as the query is constructed or past all at once when executing:

// Passing parameters in the query construction $robots= $this->modelsManager->createBuilder() ->from("Robots") ->where("name = :name:",["name" => $name]) ->andWhere("type = :type:",["type" => $type]) ->getQuery() ->execute();

// Passing parameters in query execution $robots= $this->modelsManager->createBuilder() ->from("Robots") ->where("name = :name:") ->andWhere("type = :type:") ->getQuery() ->execute(["name" => $name,"type" => $type]);

Disallow literals in PHQL

Literals can be disabled in PHQL, this means that directly using strings, numbers and boolean values in PHQL strings will be disallowed. If PHQL statements are created embedding external data on them, this could open the application to potential SQL injections:

2.3. Components 203 Phalcon PHP Framework Documentation, Release 3.1.1

$login= 'voltron';

$phql="SELECT * FROM Models\Users WHERE login = '$login'";

$result= $manager->executeQuery($phql);

If $login is changed to ' OR '' = ', the produced PHQL is:

SELECT * FROM Models\Users WHERE login = '' OR '' = ''

Which is always true no matter what the login stored in the database is. If literals are disallowed strings can be used as part of a PHQL statement, thus an exception will be thrown forcing the developer to use bound parameters. The same query can be written in a secure way like this:

$phql="SELECT Robots. * FROM Robots WHERE Robots.name = :name:";

$result= $manager->executeQuery( $phql, [ "name" => $name, ] );

You can disallow literals in the following way:

use Phalcon\Mvc\Model;

Model::setup( [ "phqlLiterals" => false ] );

Bound parameters can be used even if literals are allowed or not. Disallowing them is just another security decision a developer could take in web applications.

Escaping Reserved Words

PHQL has a few reserved words, if you want to use any of them as attributes or models names, you need to escape those words using the cross-database escaping delimiters ‘[’ and ‘]’:

$phql="SELECT * FROM [Update]"; $result= $manager->executeQuery($phql);

$phql="SELECT id, [Like] FROM Posts"; $result= $manager->executeQuery($phql);

204 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

The delimiters are dynamically translated to valid delimiters depending on the database system where the application is currently running on.

PHQL Lifecycle

Being a high-level language, PHQL gives developers the ability to personalize and customize different aspects in order to suit their needs. The following is the life cycle of each PHQL statement executed: • The PHQL is parsed and converted into an Intermediate Representation (IR) which is independent of the SQL implemented by database system • The IR is converted to valid SQL according to the database system associated to the model • PHQL statements are parsed once and cached in memory. Further executions of the same statement result in a slightly faster execution

Using Raw SQL

A database system could offer specific SQL extensions that aren’t supported by PHQL, in this case, a raw SQL can be appropriate:

sql="SELECT * FROM robots WHERE id > 0";

// Base model $robot= new Robots();

// Execute the query return new Resultset( null, $robot, $robot->getReadConnection()->query($sql) ); } }

If Raw SQL queries are common in your application a generic method could be added to your model:

2.3. Components 205 Phalcon PHP Framework Documentation, Release 3.1.1

$sql="SELECT * FROM robots WHERE $conditions";

// Base model $robot= new Robots();

// Execute the query return new Resultset( null, $robot, $robot->getReadConnection()->query($sql, $params) ); } }

The above findByRawSql could be used as follows:

$robots= Robots::findByRawSql( "id > ?", [ 10 ] );

Troubleshooting

Some things to keep in mind when using PHQL: • Classes are case-sensitive, if a class is not defined with the same name as it was created this could lead to an unexpected behavior in operating systems with case-sensitive file systems such as Linux. • Correct charset must be defined in the connection to bind parameters with success. • Aliased classes aren’t replaced by full namespaced classes since this only occurs in PHP code and not inside strings. • If column renaming is enabled avoid using column aliases with the same name as columns to be renamed, this may confuse the query resolver.

Caching in the ORM

Every application is different, we could have models whose data change frequently and others that rarely change. Accessing database systems is often one of the most common bottlenecks in terms of performance. This is due to the complex connection/communication processes that PHP must do in each request to obtain data from the database. Therefore, if we want to achieve good performance we need to add some layers of caching where the application requires it. This chapter explains the possible points where it is possible to implement caching to improve performance. The framework gives you the tools to implement the cache where you demand of it according to the architecture of your application.

206 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Caching Resultsets

A well established technique to avoid continuously accessing to the database is to cache resultsets that don’t change frequently using a system with faster access (usually memory). When Phalcon\Mvc\Model requires a service to cache resultsets, it will request it to the Dependency Injector Container with the convention name “modelsCache”. As Phalcon provides a component to cache any kind of data, we’ll explain how to integrate it with Models. First, you must register it as a service in the services container:

use Phalcon\Cache\Frontend\Data as FrontendData; use Phalcon\Cache\Backend\Memcache as BackendMemcache;

// Set the models cache service $di->set( "modelsCache", function () { // Cache data for one day by default $frontCache= new FrontendData( [ "lifetime" => 86400, ] );

// Memcached connection settings $cache= new BackendMemcache( $frontCache, [ "host" =>"localhost", "port" =>"11211", ] );

return $cache; } );

You have complete control in creating and customizing the cache before being used by registering the service as an anonymous function. Once the cache setup is properly defined you could cache resultsets as follows:

// Get products without caching $products= Products::find();

// Just cache the resultset. The cache will expire in 1 hour (3600 seconds) $products= Products::find( [ "cache" =>[ "key" =>"my-cache", ], ] );

// Cache the resultset for only for 5 minutes $products= Products::find(

2.3. Components 207 Phalcon PHP Framework Documentation, Release 3.1.1

[ "cache" =>[ "key" =>"my-cache", "lifetime" => 300, ], ] );

// Use the 'cache' service from the DI instead of 'modelsCache' $products= Products::find( [ "cache" =>[ "key" =>"my-cache", "service" =>"cache", ], ] );

Caching could be also applied to resultsets generated using relationships:

// Query some post $post= Post::findFirst();

// Get comments related to a post, also cache it $comments= $post->getComments( [ "cache" =>[ "key" =>"my-key", ], ] );

// Get comments related to a post, setting lifetime $comments= $post->getComments( [ "cache" =>[ "key" =>"my-key", "lifetime" => 3600, ], ] );

When a cached resultset needs to be invalidated, you can simply delete it from the cache using the previously specified key. Note that not all resultsets should be cached. Results that change very frequently should not be cached since they are invalidated very quickly and caching in that case impacts performance. Additionally, large datasets that do not change frequently could be cached, but that is a decision that the developer has to make based on the available caching mechanism and whether the performance impact to simply retrieve that data in the first place is acceptable.

Forcing Cache

Earlier we saw how Phalcon\Mvc\Model integrates with the caching component provided by the framework. To make a record/resultset cacheable we pass the key ‘cache’ in the array of parameters:

208 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

// Cache the resultset for only for 5 minutes $products= Products::find( [ "cache" =>[ "key" =>"my-cache", "lifetime" => 300, ], ] );

This gives us the freedom to cache specific queries, however if we want to cache globally every query performed over the model, we can override the find()/:code:`findFirst() method to force every query to be cached:

foreach ($parameters as $key => $value){ if (is_scalar($value)) { $uniqueKey[]= $key.":". $value; } elseif (is_array($value)) { $uniqueKey[]= $key.":[". self::_createKey($value)."]"; } }

return join(",", $uniqueKey); }

public static function find($parameters= null) { // Convert the parameters to an array if (!is_array($parameters)) { $parameters=[$parameters]; }

// Check if a cache key wasn't passed // and create the cache parameters if (!isset($parameters["cache"])) { $parameters["cache"]=[ "key" => self::_createKey($parameters), "lifetime" => 300, ]; }

return parent::find($parameters); }

2.3. Components 209 Phalcon PHP Framework Documentation, Release 3.1.1

public static function findFirst($parameters= null) { // ... } }

Accessing the database is several times slower than calculating a cache key. You’re free to implement any key gener- ation strategy you find to better for your needs. Note that a good key avoids collisions as much as possible - meaning that different keys should return unrelated records. This gives you full control on how the cache should be implemented for each model. If this strategy is common to several models you can create a base class for all of them:

public static function find($parameters= null) { // ... Custom caching strategy }

public static function findFirst($parameters= null) { // ... Custom caching strategy } }

Then use this class as base class for each ‘Cacheable’ model:

}

Caching PHQL Queries

Regardless of the syntax we used to create them, all queries in the ORM are handled internally using PHQL. This language gives you much more freedom to create all kinds of queries. Of course these queries can be cached:

$phql="SELECT * FROM Cars WHERE name = :name:";

$query= $this->modelsManager->createQuery($phql);

210 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

$query->cache( [ "key" =>"cars-by-name", "lifetime" => 300, ] );

$cars= $query->execute( [ "name" =>"Audi", ] );

Reusable Related Records

Some models may have relationships with other models. This allows us to easily check the records that relate to instances in memory:

// Get some invoice $invoice= Invoices::findFirst();

// Get the customer related to the invoice $customer= $invoice->customer;

// Print his/her name echo $customer->name," \n";

This example is very simple, a customer is queried and can be used as required, for example, to show its name. This also applies if we retrieve a set of invoices to show customers that correspond to these invoices:

// Get a set of invoices // SELECT * FROM invoices; $invoices= Invoices::find(); foreach ($invoices as $invoice){ // Get the customer related to the invoice // SELECT * FROM customers WHERE id = ?; $customer= $invoice->customer;

// Print his/her name echo $customer->name," \n"; }

A customer may have one or more bills so, in this example, the same customer record may be unnecessarily queried several times. To avoid this, we could mark the relationship as reusable; by doing so, we tell the ORM to automatically reuse the records from memory instead of re-querying them again and again:

2.3. Components 211 Phalcon PHP Framework Documentation, Release 3.1.1

{ public function initialize() { $this->belongsTo( "customers_id", "Customer", "id", [ "reusable" => true, ] ); } }

Note that this type of cache works in memory only, this means that cached data are released when the request is terminated.

Caching Related Records

When a related record is queried, the ORM internally builds the appropriate condition and gets the required records using find()/findFirst() in the target model according to the following table: Type Description Implicit Method Belongs-To Returns a model instance of the related record directly findFirst() Has-One Returns a model instance of the related record directly findFirst() Has-Many Returns a collection of model instances of the referenced model find() This means that when you get a related record you could intercept how the data is obtained by implementing the corresponding method:

// Get some invoice $invoice= Invoices::findFirst();

// Get the customer related to the invoice $customer= $invoice->customer; // Invoices::findFirst("...");

// Same as above $customer= $invoice->getCustomer(); // Invoices::findFirst("...");

Accordingly, we could replace the findFirst() method in the Invoices model and implement the cache we consider most appropriate:

212 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Caching Related Records Recursively

In this scenario, we assume that every time we query a result we also retrieve their associated records. If we store the records found together with their related entities perhaps we could reduce a bit the overhead required to obtain all entities:

protected static function _getCache($key) { // Returns data from a cache }

protected static function _setCache($key, $results) { // Stores data in the cache }

public static function find($parameters= null) { // Create a unique key $key= self::_createKey($parameters);

// Check if there are data in the cache $results= self::_getCache($key);

// Valid data is an object if (is_object($results)) { return $results; }

$results= [];

$invoices= parent::find($parameters);

foreach ($invoices as $invoice){ // Query the related customer $customer= $invoice->customer;

// Assign it to the record $invoice->customer= $customer;

$results[]= $invoice; }

// Store the invoices in the cache + their customers self::_setCache($key, $results);

return $results;

2.3. Components 213 Phalcon PHP Framework Documentation, Release 3.1.1

}

public function initialize() { // Add relations and initialize other stuff } }

Getting the invoices from the cache already obtains the customer data in just one hit, reducing the overall overhead of the operation. Note that this process can also be performed with PHQL following an alternative solution:

use Phalcon\Mvc\Model;

class Invoices extends Model { public function initialize() { // Add relations and initialize other stuff }

protected static function _createKey($conditions, $params) { // ... Create a cache key based on the parameters }

public function getInvoicesCustomers($conditions, $params= null) { $phql="SELECT Invoices. *, Customers.* FROM Invoices JOIN Customers WHERE". ˓→ $conditions;

$query= $this->getModelsManager()->executeQuery($phql);

$query->cache( [ "key" => self::_createKey($conditions, $params), "lifetime" => 300, ] );

return $query->execute($params); }

}

Caching based on Conditions

In this scenario, the cache is implemented differently depending on the conditions received. We might decide that the cache backend should be determined by the primary key: Type Cache Backend 1 - 10000 mongo1 10000 - 20000 mongo2 > 20000 mongo3 The easiest way is adding a static method to the model that chooses the right cache to be used:

214 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

=1&& $final< 10000){ $service="mongo1"; } elseif ($initial >= 10000&& $final <= 20000){ $service="mongo2"; } elseif ($initial> 20000){ $service="mongo3"; }

return self::find( [ "id >=". $initial." AND id <=". $final, "cache" =>[ "service" => $service, ], ] ); } }

This approach solves the problem, however, if we want to add other parameters such orders or conditions we would have to create a more complicated method. Additionally, this method does not work if the data is obtained using related records or a find()/findFirst():

$robots= Robots::find("id < 1000"); $robots= Robots::find("id > 100 AND type = 'A'"); $robots= Robots::find("(id > 100 AND type = 'A') AND id < 2000");

$robots= Robots::find( [ "(id > ?0 AND type = 'A') AND id < ?1", "bind" =>[100, 2000], "order" =>"type", ] );

To achieve this we need to intercept the intermediate representation (IR) generated by the PHQL parser and thus customize the cache everything possible: The first is create a custom builder, so we can generate a totally customized query:

2.3. Components 215 Phalcon PHP Framework Documentation, Release 3.1.1

$query= new CustomQuery($this->getPhql());

$query->setDI($this->getDI());

return $query; } }

Instead of directly returning a Phalcon\Mvc\Model\Query, our custom builder returns a CustomQuery instance, this class looks like:

parse();

// Check if the query has conditions if (isset($ir["where"])) { // The fields in the conditions can have any order // We need to recursively check the conditions tree // to find the info we're looking for $visitor= new CustomNodeVisitor();

// Recursively visits the nodes $visitor->visit($ir["where"]);

$initial= $visitor->getInitial(); $final= $visitor->getFinal();

// Select the cache according to the range // ...

// Check if the cache has data // ... }

// Execute the query $result= $this->_executeSelect($ir, $params, $types);

// Cache the result // ...

return $result; } }

Implementing a helper (CustomNodeVisitor) that recursively checks the conditions looking for fields that tell us the possible range to be used in the cache:

216 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

protected $_final= 25000;

public function visit($node) { switch ($node["type"]) { case "binary-op": $left= $this->visit($node["left"]); $right= $this->visit($node["right"]);

if (!$left ||!$right){ return false; }

if ($left ==="id"){ if ($node["op"] ===">"){ $this->_initial= $right; }

if ($node["op"] ==="="){ $this->_initial= $right; }

if ($node["op"] ===">="){ $this->_initial= $right; }

if ($node["op"] ==="<"){ $this->_final= $right; }

if ($node["op"] ==="<="){ $this->_final= $right; } }

break;

case "qualified": if ($node["name"] ==="id"){ return "id"; }

break;

case "literal": return $node["value"];

default: return false; } }

2.3. Components 217 Phalcon PHP Framework Documentation, Release 3.1.1

public function getInitial() { return $this->_initial; }

public function getFinal() { return $this->_final; } }

Finally, we can replace the find method in the Robots model to use the custom classes we’ve created:

$builder= new CustomQueryBuilder($parameters);

$builder->from(get_called_class());

$query= $builder->getQuery();

if (isset($parameters["bind"])) { return $query->execute($parameters["bind"]); } else { return $query->execute(); } } }

Caching of PHQL planning

As well as most moderns database systems PHQL internally caches the execution plan, if the same statement is executed several times PHQL reuses the previously generated plan improving performance, for a developer to take better advantage of this is highly recommended build all your SQL statements passing variable parameters as bound parameters:

$robots= $this->modelsManager->executeQuery($phql);

// ... }

218 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

In the above example, ten plans were generated increasing the memory usage and processing in the application. Rewriting the code to take advantage of bound parameters reduces the processing by both ORM and database system:

$phql="SELECT * FROM Store\Robots WHERE id = ?0"; for ($i=1; $i <= 10; $i++){ $robots= $this->modelsManager->executeQuery( $phql, [ $i, ] );

// ... }

Performance can be also improved reusing the PHQL query:

$phql="SELECT * FROM Store\Robots WHERE id = ?0";

$query= $this->modelsManager->createQuery($phql); for ($i=1; $i <= 10; $i++){ $robots= $query->execute( $phql, [ $i, ] );

// ... }

Execution plans for queries involving prepared statements are also cached by most database systems reducing the overall execution time, also protecting your application against SQL Injections.

ODM (Object-Document Mapper)

In addition to its ability to map tables in relational databases, Phalcon can map documents from NoSQL databases. The ODM offers a CRUD functionality, events, validations among other services. Due to the absence of SQL queries and planners, NoSQL databases can see real improvements in performance using the Phalcon approach. Additionally, there are no SQL building reducing the possibility of SQL injections. The following NoSQL databases are supported: Name Description MongoDB MongoDB is a scalable, high-performance, open source NoSQL database.

Creating Models

A model is a class that extends from Phalcon\Mvc\Collection. It must be placed in the models directory. A model file must contain a single class; its class name should be in camel case notation:

2.3. Components 219 Phalcon PHP Framework Documentation, Release 3.1.1

}

If you’re using PHP 5.4/5.5 is recommended declare each column that makes part of the model in order to save memory and reduce the memory allocation. By default model “Robots” will refer to the collection “robots”. If you want to manually specify another name for the mapping collection, you can use the setSource() method:

setSource("the_robots"); } }

Understanding Documents To Objects

Every instance of a model represents a document in the collection. You can easily access collection data by reading object properties. For example, for a collection “robots” with the documents:

$ mongo test MongoDB shell version:1.8.2 connecting to: test > db.robots.find() { "_id" : ObjectId("508735512d42b8c3d15ec4e1"), "name": "Astro Boy", "year": 1952, "type": "mechanical"} { "_id" : ObjectId("5087358f2d42b8c3d15ec4e2"), "name": "Bender", "year": 1999, "type": "mechanical"} { "_id" : ObjectId("508735d32d42b8c3d15ec4e3"), "name": "Wall-E", "year": 2008} >

Models in Namespaces

Namespaces can be used to avoid class name collision. In this case it is necessary to indicate the name of the related collection using the setSource() method:

220 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

{ public function initialize() { $this->setSource("robots"); } }

You could find a certain document by its ID and then print its name:

// Find record with _id = "5087358f2d42b8c3d15ec4e2" $robot= Robots::findById("5087358f2d42b8c3d15ec4e2");

// Prints "Bender" echo $robot->name;

Once the record is in memory, you can make modifications to its data and then save changes:

$robot= Robots::findFirst( [ [ "name" =>"Astro Boy", ] ] );

$robot->name="Voltron";

$robot->save();

Setting a Connection

Connections are retrieved from the services container. By default, Phalcon tries to find the connection in a service called “mongo”:

// Simple database connection to localhost $di->set( "mongo", function () { $mongo= new MongoClient();

return $mongo->selectDB("store"); }, true );

// Connecting to a domain socket, falling back to localhost connection $di->set( "mongo", function () { $mongo= new MongoClient(

2.3. Components 221 Phalcon PHP Framework Documentation, Release 3.1.1

"mongodb:///tmp/mongodb-27017.sock,localhost:27017" );

return $mongo->selectDB("store"); }, true );

Finding Documents

As Phalcon\Mvc\Collection relies on the Mongo PHP extension you have the same facilities to query documents and convert them transparently to model instances:

// How many robots are there? $robots= Robots::find(); echo "There are", count($robots)," \n";

// How many mechanical robots are there? $robots= Robots::find( [ [ "type" =>"mechanical", ] ] ); echo "There are", count($robots)," \n";

// Get and print mechanical robots ordered by name upward $robots= Robots::find( [ [ "type" =>"mechanical", ], "sort" =>[ "name" =>1, ], ] ); foreach ($robots as $robot){ echo $robot->name," \n"; }

// Get first 100 mechanical robots ordered by name $robots= Robots::find( [ [ "type" =>"mechanical", ], "sort" =>[ "name" =>1, ], "limit" => 100, ]

222 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

); foreach ($robots as $robot){ echo $robot->name," \n"; }

You could also use the findFirst() method to get only the first record matching the given criteria:

// What's the first robot in robots collection? $robot= Robots::findFirst(); echo "The robot name is", $robot->name," \n";

// What's the first mechanical robot in robots collection? $robot= Robots::findFirst( [ [ "type" =>"mechanical", ] ] ); echo "The first mechanical robot name is", $robot->name," \n";

Both find() and findFirst() methods accept an associative array specifying the search criteria:

// First robot where type = "mechanical" and year = "1999" $robot= Robots::findFirst( [ "conditions" =>[ "type" =>"mechanical", "year" =>"1999", ], ] );

// All virtual robots ordered by name downward $robots= Robots::find( [ "conditions" =>[ "type" =>"virtual", ], "sort" =>[ "name" =>-1, ], ] );

The available query options are:

2.3. Components 223 Phalcon PHP Framework Documentation, Release 3.1.1

Pa- Description Example rame- ter conditionsSearch conditions for the find operation. Is used to extract only those "conditions" => records that fulfill a specified criterion. By default Phalcon_model array('$gt' => assumes the first parameter are the conditions. 1990) fields Returns specific columns instead of the full fields in the collection. "fields" => When using this option an incomplete object is returned array('name' => true) sort It’s used to sort the resultset. Use one or more fields as each element in "sort" => the array, 1 means ordering upwards, -1 downward array("name" => -1, "status" => 1) limit Limit the results of the query to results to certain range "limit" => 10 skip Skips a number of results "skip" => 50 If you have experience with SQL databases, you may want to check the SQL to Mongo Mapping Chart.

Aggregations

A model can return calculations using aggregation framework provided by Mongo. The aggregated values are calculate without having to use MapReduce. With this option is easy perform tasks such as totaling or averaging field values:

$data= Article::aggregate( [ [ "\$project" =>[ "category" =>1, ], ], [ "\$group" =>[ "_id" =>[ "category" =>" \$category" ], "id" =>[ "\$max" =>" \$_id", ], ], ], ] );

Creating Updating/Records

The Phalcon\Mvc\Collection::save() method allows you to create/update documents according to whether they already exist in the collection associated with a model. The save() method is called internally by the create and update methods of Phalcon\Mvc\Collection. Also the method executes associated validators and events that are defined in the model:

$robot= new Robots();

224 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

$robot->type="mechanical"; $robot->name="Astro Boy"; $robot->year= 1952; if ($robot->save() === false){ echo "Umh, We can't store robots right now: \n";

$messages= $robot->getMessages();

foreach ($messages as $message){ echo $message," \n"; } } else { echo "Great, a new robot was saved successfully!"; }

The “_id” property is automatically updated with the MongoId object created by the driver:

$robot->save(); echo "The generated id is:", $robot->getId();

Validation Messages

Phalcon\Mvc\Collection has a messaging subsystem that provides a flexible way to output or store the validation messages generated during the insert/update processes. Each message consists of an instance of the class Phalcon\Mvc\Model\Message. The set of messages generated can be retrieved with the method getMessages(). Each message provides extended information like the field name that generated the message or the message type:

save() === false){ $messages= $robot->getMessages();

foreach ($messages as $message){ echo "Message:", $message->getMessage(); echo "Field:", $message->getField(); echo "Type:", $message->getType(); } }

Validation Events and Events Manager

Models allow you to implement events that will be thrown when performing an insert or update. They help define business rules for a certain model. The following are the events supported by Phalcon\Mvc\Collection and their order of execution:

2.3. Components 225 Phalcon PHP Framework Documentation, Release 3.1.1

Opera- Name Can stop Explanation tion operation? Insert- beforeValidationYES Is executed before the validation process and the final ing/Updating insert/update to the database Inserting beforeValidationOnCreateYES Is executed before the validation process only when an insertion operation is being made Updating beforeValidationOnUpdateYES Is executed before the fields are validated for not nulls or foreign keys when an updating operation is being made Insert- onValidationFailsYES Is executed before the validation process only when an ing/Updating (already insertion operation is being made stopped) Inserting afterValidationOnCreateYES Is executed after the validation process when an insertion operation is being made Updating afterValidationOnUpdateYES Is executed after the validation process when an updating operation is being made Insert- afterValidation YES Is executed after the validation process ing/Updating Insert- beforeSave YES Runs before the required operation over the database system ing/Updating Updating beforeUpdate YES Runs before the required operation over the database system only when an updating operation is being made Inserting beforeCreate YES Runs before the required operation over the database system only when an inserting operation is being made Updating afterUpdate NO Runs after the required operation over the database system only when an updating operation is being made Inserting afterCreate NO Runs after the required operation over the database system only when an inserting operation is being made Insert- afterSave NO Runs after the required operation over the database system ing/Updating To make a model to react to an event, we must to implement a method with the same name of the event:

Events can be useful to assign values before performing an operation, for example:

created_at= date("Y-m-d H:i:s");

226 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

}

public function beforeUpdate() { // Set the modification date $this->modified_in= date("Y-m-d H:i:s"); } }

Additionally, this component is integrated with Phalcon\Events\Manager, this means we can create listeners that run when an event is triggered.

$eventsManager= new EventsManager();

// Attach an anonymous function as a listener for "model" events $eventsManager->attach( "collection:beforeSave", function (Event $event, $robot){ if ($robot->name ==="Scooby Doo"){ echo "Scooby Doo isn't a robot!";

return false; }

return true; } );

$robot= new Robots();

$robot->setEventsManager($eventsManager);

$robot->name="Scooby Doo"; $robot->year= 1969;

$robot->save();

In the example given above the EventsManager only acted as a bridge between an object and a listener (the anonymous function). If we want all objects created in our application use the same EventsManager, then we need to assign this to the Models Manager:

// Registering the collectionManager service $di->set( "collectionManager", function () { $eventsManager= new EventsManager();

2.3. Components 227 Phalcon PHP Framework Documentation, Release 3.1.1

// Attach an anonymous function as a listener for "model" events $eventsManager->attach( "collection:beforeSave", function (Event $event, $model){ if (get_class($model) ==="Robots"){ if ($model->name ==="Scooby Doo"){ echo "Scooby Doo isn't a robot!";

return false; } }

return true; } );

// Setting a default EventsManager $modelsManager= new CollectionManager();

$modelsManager->setEventsManager($eventsManager);

return $modelsManager; }, true );

Implementing a Business Rule

When an insert, update or delete is executed, the model verifies if there are any methods with the names of the events listed in the table above. We recommend that validation methods are declared protected to prevent that business logic implementation from being exposed publicly. The following example implements an event that validates the year cannot be smaller than 0 on update or insert:

use Phalcon\Mvc\Collection;

class Robots extends Collection { public function beforeSave() { if ($this->year<0){ echo "Year cannot be smaller than zero!";

return false; } } }

Some events return false as an indication to stop the current operation. If an event doesn’t return anything, Phal- con\Mvc\Collection will assume a true value.

228 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Validating Data Integrity

Phalcon\Mvc\Collection provides several events to validate data and implement business rules. The special “valida- tion” event allows us to call built-in validators over the record. Phalcon exposes a few built-in validators that can be used at this stage of validation. The following example shows how to use it:

$validation->add( "type", new InclusionIn( [ "message" =>"Type must be: mechanical or virtual", "domain" =>[ "Mechanical", "Virtual", ], ] ) );

$validation->add( "price", new Numericality( [ "message" =>"Price must be numeric" ] ) );

return $this->validate($validation); } }

The example given above performs a validation using the built-in validator “InclusionIn”. It checks the value of the field “type” in a domain list. If the value is not included in the method, then the validator will fail and return false. For more information on validators, see the Validation documentation.

Deleting Records

The Phalcon\Mvc\Collection::delete() method allows you to delete a document. You can use it as fol- lows:

2.3. Components 229 Phalcon PHP Framework Documentation, Release 3.1.1

$robot= Robots::findFirst(); if ($robot !== false){ if ($robot->delete() === false){ echo "Sorry, we can't delete the robot right now: \n";

$messages= $robot->getMessages();

foreach ($messages as $message){ echo $message," \n"; } } else { echo "The robot was deleted successfully!"; } }

You can also delete many documents by traversing a resultset with a foreach loop:

$robots= Robots::find( [ [ "type" =>"mechanical", ] ] ); foreach ($robots as $robot){ if ($robot->delete() === false){ echo "Sorry, we can't delete the robot right now: \n";

$messages= $robot->getMessages();

foreach ($messages as $message){ echo $message," \n"; } } else { echo "The robot was deleted successfully!"; } }

The following events are available to define custom business rules that can be executed when a delete operation is performed: Operation Name Can stop operation? Explanation Deleting beforeDelete YES Runs before the delete operation is made Deleting afterDelete NO Runs after the delete operation was made

Validation Failed Events

Another type of events is available when the data validation process finds any inconsistency:

230 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Operation Name Explanation Insert or Update notSave Triggered when the insert/update operation fails for any reason Insert, Delete or onValidationFails Triggered when any data manipulation operation fails Update

Implicit Ids vs. User Primary Keys

By default Phalcon\Mvc\Collection assumes that the _id attribute is automatically generated using MongoIds. If a model uses custom primary keys this behavior can be overridden:

use Phalcon\Mvc\Collection;

class Robots extends Collection { public function initialize() { $this->useImplicitObjectIds(false); } }

Setting multiple databases

In Phalcon, all models can belong to the same database connection or have an individual one. Actually, when Phal- con\Mvc\Collection needs to connect to the database it requests the “mongo” service in the application’s services container. You can overwrite this service setting it in the initialize method:

// This service returns a mongo database at 192.168.1.100 $di->set( "mongo1", function () { $mongo= new MongoClient( "mongodb://scott:[email protected]" );

return $mongo->selectDB("management"); }, true );

// This service returns a mongo database at localhost $di->set( "mongo2", function () { $mongo= new MongoClient( "mongodb://localhost" );

return $mongo->selectDB("invoicing"); },

2.3. Components 231 Phalcon PHP Framework Documentation, Release 3.1.1

true );

Then, in the initialize() method, we define the connection service for the model:

setConnectionService("mongo1"); } }

Injecting services into Models

You may be required to access the application services within a model, the following example explains how to do that:

getDI()->getShared("flash");

$messages= $this->getMessages();

// Show validation messages foreach ($messages as $message){ $flash->error( (string) $message ); } } }

The “notSave” event is triggered whenever a “creating” or “updating” action fails. We’re flashing the validation messages obtaining the “flash” service from the DI container. By doing this, we don’t have to print messages after each saving.

Using Views

Views represent the user interface of your application. Views are often HTML files with embedded PHP code that perform tasks related solely to the presentation of the data. Views handle the job of providing data to the web browser or other tool that is used to make requests from your application. Phalcon\Mvc\View and Phalcon\Mvc\View\Simple are responsible for the managing the view layer of your MVC ap- plication.

232 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Integrating Views with Controllers

Phalcon automatically passes the execution to the view component as soon as a particular controller has completed its cycle. The view component will look in the views folder for a folder named as the same name of the last con- troller executed and then for a file named as the last action executed. For instance, if a request is made to the URL http://127.0.0.1/blog/posts/show/301, Phalcon will parse the URL as follows: Server Address 127.0.0.1 Phalcon Directory blog Controller posts Action show Parameter 301 The dispatcher will look for a “PostsController” and its action “showAction”. A simple controller file for this example:

}

public function showAction($postId) { // Pass the $postId parameter to the view $this->view->postId= $postId; } }

The setVar() method allows us to create view variables on demand so that they can be used in the view template. The example above demonstrates how to pass the $postId parameter to the respective view template.

Hierarchical Rendering

Phalcon\Mvc\View supports a hierarchy of files and is the default component for view rendering in Phalcon. This hierarchy allows for common layout points (commonly used views), as well as controller named folders defining respective view templates. This component uses by default PHP itself as the template engine, therefore views should have the .phtml extension. If the views directory is app/views then view component will find automatically for these 3 view files. Name File Description Action app/views/posts/show.phtmlThis is the view related to the action. It only will be shown when the “show” action View was executed. Con- app/views/layouts/posts.phtmlThis is the view related to the controller. It only will be shown for every action troller executed within the controller “posts”. All the code implemented in the layout will Layout be reused for all the actions in this controller. Main app/views/index.phtmlThis is main action it will be shown for every controller or action executed within Layout the application. You are not required to implement all of the files mentioned above. Phalcon\Mvc\View will simply move to the next view level in the hierarchy of files. If all three view files are implemented, they will be processed as follows:

2.3. Components 233 Phalcon PHP Framework Documentation, Release 3.1.1

This is show view!

I have received the parameter

This is the "posts" controller layout!

getContent(); ?>

Example

This is main layout!

getContent(); ?>

Note the lines where the method $this->getContent() was called. This method instructs Phalcon\Mvc\View on where to inject the contents of the previous view executed in the hierarchy. For the example above, the output will be: The generated HTML by the request will be:

Example

This is main layout!

This is the "posts" controller layout!

This is show view!

I have received the parameter 101

234 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Using Templates

Templates are views that can be used to share common view code. They act as controller layouts, so you need to place them in the layouts directory. Templates can be rendered before the layout (using $this->view->setTemplateBefore()) or they can be rendered after the layout (using this->view->setTemplateAfter()). In the following example the template (layouts/common.phtml) is rendered after the main layout (layouts/posts.phtml):

view->setTemplateAfter("common"); }

public function lastAction() { $this->flash->notice( "These are the latest posts" ); } }

2.3. Components 235 Phalcon PHP Framework Documentation, Release 3.1.1

Blog's title getContent(); ?>

getContent(); ?>

Blog Title

getContent(); ?>

This is a title

This is the post content

This is another title

This is another post content

The final output will be the following:

Blog's title

236 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Blog Title

This is a title

This is the post content

This is another title

This is another post content

If we had used $this->view->setTemplateBefore("common"), this would be the final output:

Blog's title

Blog Title

This is a title

This is the post content

This is another title

This is another post content

2.3. Components 237 Phalcon PHP Framework Documentation, Release 3.1.1

Control Rendering Levels

As seen above, Phalcon\Mvc\View supports a view hierarchy. You might need to control the level of rendering pro- duced by the view component. The method Phalcon\Mvc\View::setRenderLevel() offers this functional- ity. This method can be invoked from the controller or from a superior view layer to interfere with the rendering process.

}

public function findAction() { // This is an Ajax response so it doesn't generate any kind of view $this->view->setRenderLevel( View::LEVEL_NO_RENDER );

// ... }

public function showAction($postId) { // Shows only the view related to the action $this->view->setRenderLevel( View::LEVEL_ACTION_VIEW ); } }

The available render levels are:

238 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Class Constant Description Or- der LEVEL_NO_RENDER Indicates to avoid generating any kind of presentation. LEVEL_ACTION_VIEW Generates the presentation to the view associated to the action. 1 LEVEL_BEFORE_TEMPLATE Generates presentation templates prior to the controller layout. 2 LEVEL_LAYOUT Generates the presentation to the controller layout. 3 LEVEL_AFTER_TEMPLATE Generates the presentation to the templates after the controller 4 layout. LEVEL_MAIN_LAYOUT Generates the presentation to the main layout. File 5 views/index.phtml

Disabling render levels

You can permanently or temporarily disable render levels. A level could be permanently disabled if it isn’t used at all in the whole application:

use Phalcon\Mvc\View;

$di->set( "view", function () { $view= new View();

// Disable several levels $view->disableLevel( [ View::LEVEL_LAYOUT => true, View::LEVEL_MAIN_LAYOUT => true, ] );

return $view; }, true );

Or disable temporarily in some part of the application:

use Phalcon\Mvc\View; use Phalcon\Mvc\Controller;

class PostsController extends Controller { public function indexAction() {

}

public function findAction() { $this->view->disableLevel( View::LEVEL_MAIN_LAYOUT

2.3. Components 239 Phalcon PHP Framework Documentation, Release 3.1.1

); } }

Picking Views

As mentioned above, when Phalcon\Mvc\View is managed by Phalcon\Mvc\Application the view rendered is the one related with the last controller and action executed. You could override this by using the Phalcon\Mvc\View::pick() method:

view->pick("products/search");

// Pick "views-dir/books/list" as view to render $this->view->pick( [ "books", ] );

// Pick "views-dir/products/search" as view to render $this->view->pick( [ 1 =>"search", ] ); } }

Disabling the view

If your controller doesn’t produce any output in the view (or not even have one) you may disable the view component avoiding unnecessary processing:

// Disable the view to avoid rendering

240 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

$this->view->disable(); } }

Alternatively, you can return false to produce the same effect:

use Phalcon\Mvc\Controller;

class UsersController extends Controller { public function closeSessionAction() { // ...

// Disable the view to avoid rendering return false; } }

You can return a ‘response’ object to avoid disable the view manually:

use Phalcon\Mvc\Controller;

class UsersController extends Controller { public function closeSessionAction() { // Close session // ...

// A HTTP Redirect return $this->response->redirect("index/index"); } }

Simple Rendering

Phalcon\Mvc\View\Simple is an alternative component to Phalcon\Mvc\View. It keeps most of the philosophy of Phalcon\Mvc\View but lacks of a hierarchy of files which is, in fact, the main feature of its counterpart. This component allows the developer to have control of when a view is rendered and its location. In addition, this component can leverage of view inheritance available in template engines such as Volt and others. The default component must be replaced in the service container:

use Phalcon\Mvc\View\Simple as SimpleView;

$di->set( "view", function () { $view= new SimpleView();

2.3. Components 241 Phalcon PHP Framework Documentation, Release 3.1.1

$view->setViewsDir("../app/views/");

return $view; }, true );

Automatic rendering must be disabled in Phalcon\Mvc\Application (if needed):

$application->useImplicitView(false);

$response= $application->handle();

$response->send(); } catch (Exception $e){ echo $e->getMessage(); }

To render a view it’s necessary to call the render method explicitly indicating the relative path to the view you want to display:

view->render("index");

// Render 'views-dir/posts/show.phtml' echo $this->view->render("posts/show");

// Render 'views-dir/index.phtml' passing variables echo $this->view->render( "index", [ "posts" => Posts::find(), ] );

// Render 'views-dir/posts/show.phtml' passing variables echo $this->view->render( "posts/show", [ "posts" => Posts::find(), ]

242 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

); } }

This is different to Phalcon\Mvc\View who’s render() method uses controllers and actions as parameters:

$params=[ "posts" => Posts::find(), ];

// Phalcon\Mvc\View $view= new \Phalcon\Mvc\View(); echo $view->render("posts","show", $params);

// Phalcon\Mvc\View\Simple $simpleView= new \Phalcon\Mvc\View\Simple(); echo $simpleView->render("posts/show", $params);

Using Partials

Partial templates are another way of breaking the rendering process into simpler more manageable chunks that can be reused by different parts of the application. With a partial, you can move the code for rendering a particular piece of a response to its own file. One way to use partials is to treat them as the equivalent of subroutines: as a way to move details out of a view so that your code can be more easily understood. For example, you might have a view that looks like this:

partial("shared/ad_banner"); ?>

Robots

Check out our specials for robots:

...

The partial() method does accept a second parameter as an array of variables/parameters that only will exists in the scope of the partial:

partial("shared/ad_banner",["id" => $site->id,"size" =>"big"]); ?>

Transfer values from the controller to views

Phalcon\Mvc\View is available in each controller using the view variable ($this->view). You can use that object to set variables directly to the view from a controller action by using the setVar() method.

use Phalcon\Mvc\Controller;

class PostsController extends Controller

2.3. Components 243 Phalcon PHP Framework Documentation, Release 3.1.1

{ public function indexAction() {

}

public function showAction() { $user= Users::findFirst(); $posts= $user->getPosts();

// Pass all the username and the posts to the views $this->view->setVar("username", $user->username); $this->view->setVar("posts", $posts;

// Using the magic setter $this->view->username= $user->username; $this->view->posts= $posts;

// Passing more than one variable at the same time $this->view->setVars( [ "username" => $user->username, "posts" => $posts, ] ); } }

A variable with the name of the first parameter of setVar() will be created in the view, ready to be used. The variable can be of any type, from a simple string, integer etc. variable to a more complex structure such as array, collection etc.

{{ username }}'s Posts

foreach ($posts as $post){ echo "

", $post->title,"

"; }

?>

Caching View Fragments

Sometimes when you develop dynamic websites and some areas of them are not updated very often, the output is exactly the same between requests. Phalcon\Mvc\View offers caching a part or the whole rendered output to increase performance. Phalcon\Mvc\View integrates with Phalcon\Cache to provide an easier way to cache output fragments. You could manually set the cache handler or set a global handler:

244 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

view->cache(true); }

public function showArticleAction() { // Cache this view for 1 hour $this->view->cache( [ "lifetime" => 3600, ] ); }

public function resumeAction() { // Cache this view for 1 day with the key "resume-cache" $this->view->cache( [ "lifetime" => 86400, "key" =>"resume-cache", ] ); }

public function downloadAction() { // Passing a custom service $this->view->cache( [ "service" =>"myCache", "lifetime" => 86400, "key" =>"resume-cache", ] ); } }

When we do not define a key to the cache, the component automatically creates one using an MD5 hash of the name of the controller and view currently being rendered in the format of “controller/view”. It is a good practice to define a key for each action so you can easily identify the cache associated with each view. When the View component needs to cache something it will request a cache service from the services container. The service name convention for this service is “viewCache”:

2.3. Components 245 Phalcon PHP Framework Documentation, Release 3.1.1

// Set the views cache service $di->set( "viewCache", function () { // Cache data for one day by default $frontCache= new OutputFrontend( [ "lifetime" => 86400, ] );

// Memcached connection settings $cache= new MemcacheBackend( $frontCache, [ "host" =>"localhost", "port" =>"11211", ] );

return $cache; } );

The frontend must always be Phalcon\Cache\Frontend\Output and the service ‘viewCache’ must be reg- istered as always open (not shared) in the services container (DI). When using views, caching can be used to prevent controllers from needing to generate view data on each request. To achieve this we must identify uniquely each cache with a key. First we verify that the cache does not exist or has expired to make the calculations/queries to display data in the view:

view->getCache()->exists("downloads")) { // Query the latest downloads $latest= Downloads::find( [ "order" =>"created_at DESC", ] );

$this->view->latest= $latest; }

// Enable the cache with the same key "downloads" $this->view->cache( [ "key" =>"downloads", ] );

246 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

} }

The PHP alternative site is an example of implementing the caching of fragments.

Template Engines

Template Engines help designers to create views without the use of a complicated syntax. Phalcon includes a powerful and fast templating engine called Volt. Additionally, Phalcon\Mvc\View allows you to use other template engines instead of plain PHP or Volt. Using a different template engine, usually requires complex text parsing using external PHP libraries in order to generate the final output for the user. This usually increases the number of resources that your application will use. If an external template engine is used, Phalcon\Mvc\View provides exactly the same view hierarchy and it’s still possible to access the API inside these templates with a little more effort. This component uses adapters, these help Phalcon to speak with those external template engines in a unified way, let’s see how to do that integration.

Creating your own Template Engine Adapter

There are many template engines, which you might want to integrate or create one of your own. The first step to start using an external template engine is create an adapter for it. A template engine adapter is a class that acts as bridge between Phalcon\Mvc\View and the template engine itself. Usually it only needs two methods implemented: __construct() and render(). The first one receives the Phalcon\Mvc\View instance that creates the engine adapter and the DI container used by the application. The method render() accepts an absolute path to the view file and the view parameters set using $this->view->setVar(). You could read or require it when it’s necessary.

/** * Renders a view using the template engine * * @param string $path * @param array $params

2.3. Components 247 Phalcon PHP Framework Documentation, Release 3.1.1

*/ public function render($path, $params) { // Access view $view= $this->_view;

// Access options $options= $this->_options;

// Render the view // ... } }

Changing the Template Engine

You can replace the template engine completely or use more than one template engine at the same time. The method Phalcon\Mvc\View::registerEngines() accepts an array containing data that define the template engines. The key of each engine is an extension that aids in distinguishing one from another. Template files related to the particular engine must have those extensions. The order that the template engines are defined with Phalcon\Mvc\View::registerEngines() defines the relevance of execution. If Phalcon\Mvc\View finds two views with the same name but different extensions, it will only render the first one. If you want to register a template engine or a set of them for each request in the application. You could register it when the view service is created:

use Phalcon\Mvc\View;

// Setting up the view component $di->set( "view", function () { $view= new View();

// A trailing directory separator is required $view->setViewsDir("../app/views/");

// Set the engine $view->registerEngines( [ ".my-html" =>"MyTemplateAdapter", ] );

// Using more than one template engine $view->registerEngines( [ ".my-html" =>"MyTemplateAdapter", ".phtml" =>"Phalcon \\Mvc\\View\\Engine\\Php", ] );

248 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

return $view; }, true );

There are adapters available for several template engines on the Phalcon Incubator

Injecting services in View

Every view executed is included inside a Phalcon\Di\Injectable instance, providing easy access to the application’s service container. The following example shows how to write a jQuery ajax request using a URL with the framework conventions. The service “url” (usually Phalcon\Mvc\Url) is injected in the view by accessing a property with the same name:

Stand-Alone Component

All the components in Phalcon can be used as glue components individually because they are loosely coupled to each other:

Hierarchical Rendering

Using Phalcon\Mvc\View in a stand-alone mode can be demonstrated below:

use Phalcon\Mvc\View;

$view= new View();

// A trailing directory separator is required $view->setViewsDir("../app/views/");

// Passing variables to the views, these will be created as local variables $view->setVar("someProducts", $products); $view->setVar("someFeatureEnabled", true);

// Start the output buffering $view->start();

// Render all the view hierarchy related to the view products/list.phtml $view->render("products","list");

2.3. Components 249 Phalcon PHP Framework Documentation, Release 3.1.1

// Finish the output buffering $view->finish(); echo $view->getContent();

A short syntax is also available:

$view= new View(); echo $view->getRender( "products", "list", [ "someProducts" => $products, "someFeatureEnabled" => true, ], function ($view){ // Set any extra options here

$view->setViewsDir("../app/views/");

$view->setRenderLevel( View::LEVEL_LAYOUT ); } );

Simple Rendering

Using Phalcon\Mvc\View\Simple in a stand-alone mode can be demonstrated below:

$view= new SimpleView();

// A trailing directory separator is required $view->setViewsDir("../app/views/");

// Render a view and return its contents as a string echo $view->render("templates/welcomeMail");

// Render a view passing parameters echo $view->render( "templates/welcomeMail", [ "email" => $email, "content" => $content, ] );

250 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

View Events

Phalcon\Mvc\View and Phalcon\Mvc\View\Simple are able to send events to an EventsManager if it is present. Events are triggered using the type “view”. Some events when returning boolean false could stop the active operation. The following events are supported: Event Name Triggered Can stop operation? beforeRender Triggered before starting the render process Yes beforeRenderView Triggered before rendering an existing view Yes afterRenderView Triggered after rendering an existing view No afterRender Triggered after completing the render process No notFoundView Triggered when a view was not found No The following example demonstrates how to attach listeners to this component:

use Phalcon\Events\Event; use Phalcon\Events\Manager as EventsManager; use Phalcon\Mvc\View;

$di->set( "view", function () { // Create an events manager $eventsManager= new EventsManager();

// Attach a listener for type "view" $eventsManager->attach( "view", function (Event $event, $view){ echo $event->getType(),"-", $view->getActiveRenderPath(), PHP_EOL; } );

$view= new View();

$view->setViewsDir("../app/views/");

// Bind the eventsManager to the view component $view->setEventsManager($eventsManager);

return $view; }, true );

The following example shows how to create a plugin that clean/repair the HTML produced by the render process using Tidy:

use Phalcon\Events\Event;

class TidyPlugin { public function afterRender(Event $event, $view) {

2.3. Components 251 Phalcon PHP Framework Documentation, Release 3.1.1

$tidyConfig=[ "clean" => true, "output-xhtml" => true, "show-body-only" => true, "wrap" =>0, ];

$tidy= tidy_parse_string( $view->getContent(), $tidyConfig, "UTF8" );

$tidy->cleanRepair();

$view->setContent( (string) $tidy ); } }

// Attach the plugin as a listener $eventsManager->attach( "view:afterRender", new TidyPlugin() );

View Helpers (Tags)

Writing and maintaining HTML markup can quickly become a tedious task because of the naming conventions and nu- merous attributes that have to be taken into consideration. Phalcon deals with this complexity by offering Phalcon\Tag, which in turn offers view helpers to generate HTML markup. This component can be used in a plain HTML+PHP view or in a Volt template. This guide is not intended to be a complete documentation of available helpers and their arguments. Please visit the Phalcon\Tag page in the API for a complete reference.

Document Type of Content

Phalcon provides Phalcon\Tag::setDoctype() helper to set document type of the content. Document type setting may affect HTML output produced by other tag helpers. For example, if you set XHTML document type , helpers that return or output HTML tags will produce self-closing tags to follow valid XHTML standard. Available document type constants in Phalcon\Tag namespace are:

252 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Constant Document type HTML32 HTML 3.2 HTML401_STRICT HTML 4.01 Strict HTML401_TRANSITIONAL HTML 4.01 Transitional HTML401_FRAMESET HTML 4.01 Frameset HTML5 HTML 5 XHTML10_STRICT XHTML 1.0 Strict XHTML10_TRANSITIONAL XHTML 1.0 Transitional XHTML10_FRAMESET XHTML 1.0 Frameset XHTML11 XHTML 1.1 XHTML20 XHTML 2.0 XHTML5 XHTML 5 Setting document type.

$this->tag->setDoctype(Tag::HTML401_STRICT);

?>

Getting document type.

tag->getDoctype() ?>

The following HTML will be produced.

Volt syntax:

{{ get_doctype() }}

Generating Links

A real common task in any web application or website is to produce links that allow us to navigate from one page to another. When they are internal URLs we can create them in the following manner:

tag->linkTo("products/search","Search") ?>

tag->linkTo(["products/edit/10","Edit","class" =>"edit-btn"]) ?>

2.3. Components 253 Phalcon PHP Framework Documentation, Release 3.1.1

tag->linkTo([["for" =>"show-product","title" => 123,"name" =>"carrots

˓→"],"Show"]) ?>

Actually, all produced URLs are generated by the component Phalcon\Mvc\Url (or service “url” failing) Same links generated with Volt:

{{ link_to("products/search", "Search") }}

{{ link_to(["for": "show-product", "id":123, "name": "carrots"], "Show") }}

{{ link_to(["for": "show-product", "id":123, "name": "carrots"], "Show", "class":

˓→"edit-btn") }}

Creating Forms

Forms in web applications play an essential part in retrieving user input. The following example shows how to implement a simple search form using view helpers:

tag->form("products/search") ?>

tag->textField("q") ?>

tag->submitButton("Search") ?> tag->endForm() ?>

tag->form(["products/search","method" =>"get"]); ?>

tag->textField("q"); ?>

tag->submitButton("Search"); ?> tag->endForm() ?>

This last code will generate the following HTML:

Same form generated in Volt:

{{ form("products/search", "method": "get") }}

254 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

{{ text_field("q") }}

{{ submit_button("Search") }} {{ endForm() }}

Phalcon also provides a form builder to create forms in an object-oriented manner.

Helpers to Generate Form Elements

Phalcon provides a series of helpers to generate form elements such as text fields, buttons and more. The first parameter of each helper is always the name of the element to be generated. When the form is submitted, the name will be passed along with the form data. In a controller you can get these values using the same name by using the getPost() and getQuery() methods on the request object ($this->request).

tag->textField("username") ?>

tag->textArea( [ "comment", "This is the content of the text-area", "cols" =>"6", "rows" => 20, ] ) ?>

tag->passwordField( [ "password", "size" => 30, ] ) ?>

tag->hiddenField( [ "parent_id", "value" =>"5", ] ) ?>

Volt syntax:

{{ text_field("username") }}

{{ text_area("comment", "This is the content", "cols": "6", "rows":20) }}

{{ password_field("password", "size":30) }}

{{ hidden_field("parent_id", "value": "5") }}

Making Select Boxes

Generating select boxes (select box) is easy, especially if the related data is stored in PHP associative arrays. The helpers for select elements are Phalcon\Tag::select() and Phalcon\Tag::selectStatic(). Phalcon\Tag::select() has been was specifically designed to work with Phalcon\Mvc\Model, while Phalcon\Tag::selectStatic() can with PHP arrays.

2.3. Components 255 Phalcon PHP Framework Documentation, Release 3.1.1

$products= Products::find("type = 'vegetables'");

// Using data from a resultset echo $this->tag->select( [ "productId", $products, "using" =>[ "id", "name", ] ] );

// Using data from an array echo $this->tag->selectStatic( [ "status", [ "A" =>"Active", "I" =>"Inactive", ] ] );

The following HTML will generated:

You can add an “empty” option to the generated HTML:

$products= Products::find("type = 'vegetables'");

// Creating a Select Tag with an empty option echo $this->tag->select( [ "productId", $products, "using" =>[ "id", "name", ], "useEmpty" => true, ] );

256 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Produces this HTML:

$products= Products::find("type = 'vegetables'");

// Creating a Select Tag with an empty option with default text echo $this->tag->select( [ "productId", $products, "using" =>[ "id", "name", ], "useEmpty" => true, "emptyText" =>"Please, choose one...", "emptyValue" =>"@", ] );

Volt syntax for above example:

{# Creating a Select Tag with an empty option with default text #} {{ select('productId', products, 'using':['id', 'name'], 'useEmpty': true, 'emptyText': 'Please, choose one...', 'emptyValue': '@') }}

Assigning HTML attributes

All the helpers accept an array as their first parameter which can contain additional HTML attributes for the element generated.

tag->textField( [ "price", "size" => 20, "maxlength" => 30, "placeholder" =>"Enter a price", ] ) ?> or using Volt:

2.3. Components 257 Phalcon PHP Framework Documentation, Release 3.1.1

{{ text_field("price", "size":20, "maxlength":30, "placeholder": "Enter a price") }}

The following HTML is generated:

Setting Helper Values

From Controllers

It is a good programming principle for MVC frameworks to set specific values for form elements in the view. You can set those values directly from the controller using Phalcon\Tag::setDefault(). This helper preloads a value for any helpers present in the view. If any helper in the view has a name that matches the preloaded value, it will use it, unless a value is directly assigned on the helper in the view.

use Phalcon\Mvc\Controller;

class ProductsController extends Controller { public function indexAction() { $this->tag->setDefault("color","Blue"); } }

At the view, a selectStatic helper matches the same index used to preset the value. In this case “color”:

echo $this->tag->selectStatic( [ "color", [ "Yellow" =>"Yellow", "Blue" =>"Blue", "Red" =>"Red", ] ] );

This will generate the following select tag with the value “Blue” selected:

258 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

From the Request

A special feature that the Phalcon\Tag helpers have is that they keep the values of form helpers between requests. This way you can easily show validation messages without losing entered data.

Specifying values directly

Every form helper supports the parameter “value”. With it you can specify a value for the helper directly. When this parameter is present, any preset value using setDefault() or via request will be ignored.

Changing dynamically the Document Title

Phalcon\Tag offers helpers to change dynamically the document title from the controller. The following example demonstrates just that:

use Phalcon\Mvc\Controller;

class PostsController extends Controller { public function initialize() { $this->tag->setTitle("Your Website"); }

public function indexAction() { $this->tag->prependTitle("Index of Posts -"); } }

tag->getTitle(); ?>

The following HTML will generated:

Index of Posts - Your Website

2.3. Components 259 Phalcon PHP Framework Documentation, Release 3.1.1

Static Content Helpers

Phalcon\Tag also provide helpers to generate tags such as script, link or img. They aid in quick and easy generation of the static resources of your application

Images

// Generate echo $this->tag->image("img/hello.gif");

// Generate echo $this->tag->image( [ "img/hello.gif", "alt" =>"alternative text", ] );

Volt syntax:

{# Generate #} {{ image("img/hello.gif") }}

{# Generate #} {{ image("img/hello.gif", "alt": "alternative text") }}

Stylesheets

// Generate echo $this->tag->stylesheetLink("http://fonts.googleapis.com/css?family=Rosario",

˓→false);

// Generate echo $this->tag->stylesheetLink("css/styles.css");

Volt syntax:

{# Generate #} {{ stylesheet_link("http://fonts.googleapis.com/css?family=Rosario", false) }}

{# Generate #} {{ stylesheet_link("css/styles.css") }}

260 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

Javascript

// Generate echo $this->tag->javascriptInclude("http://localhost/javascript/jquery.min.js",

˓→false);

// Generate #} {{ javascript_include("http://localhost/javascript/jquery.min.js", false) }}

{# Generate ";

// CSS class name yang jahat $className=";`(";

// CSS font name jahat $fontName="Verdana \"";

// Javascript text jahat $javascriptText="';Hello";

// Mmebuat escaper $e= new Escaper();

?>

<?php echo $e->escapeHtml($maliciousTitle); ?>

380 Chapter 2. Daftar Isi Phalcon PHP Framework Documentation, Release 3.1.1

hello

Yang menghasilkan berikut ini:

</title><script>alert(1)</script>

hello

Tiap teks diescape berdasarkan konteksnya masing-masing. Menggunakan konteks yang tepat penting untuk menghin- dari serangan XSS.

2.3. Components 381 Phalcon PHP Framework Documentation, Release 3.1.1

Escape HTML

Situasi umum ketika menyisipkan data tidak aman adalah antara tag HTML:

Anda dapat escape data tersebut dengan metode escapeHtml:

escapeHtml('>

myattack

'); ?>